diff --git a/3rdparty/ext_exiv2/CMakeLists.txt b/3rdparty/ext_exiv2/CMakeLists.txt index 29c51c7ed6..eb9d9e6b23 100644 --- a/3rdparty/ext_exiv2/CMakeLists.txt +++ b/3rdparty/ext_exiv2/CMakeLists.txt @@ -1,16 +1,30 @@ SET(PREFIX_ext_exiv2 "${EXTPREFIX}" ) - +if (WIN32) ExternalProject_Add( ext_exiv2 - DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://files.kde.org/krita/build/dependencies/exiv2-0.26-trunk.tar.gz - URL_MD5 5399e3b570d7f9205f0e76d47582da4c + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL http://files.kde.org/krita/build/dependencies/exiv2-0.26-trunk.tar.gz + URL_MD5 5399e3b570d7f9205f0e76d47582da4c PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/tzname.patch - COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/patch_mingw.patch + COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/patch_mingw.patch + COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/build_with_mingw8.diff + + INSTALL_DIR ${PREFIX_ext_exiv2} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_exiv2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DEXIV2_ENABLE_BUILD_SAMPLES=OFF -DEXIV2_ENABLE_BUILD_PO=OFF -DEXIV2_ENABLE_NLS=OFF -DICONV_INCLUDE_DIR=${PREFIX_ext_exiv2}/include + + UPDATE_COMMAND "" + DEPENDS ext_iconv ext_expat +) +else() +ExternalProject_Add( ext_exiv2 + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL http://files.kde.org/krita/build/dependencies/exiv2-0.26-trunk.tar.gz + URL_MD5 5399e3b570d7f9205f0e76d47582da4c - INSTALL_DIR ${PREFIX_ext_exiv2} - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_exiv2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DEXIV2_ENABLE_BUILD_SAMPLES=OFF -DEXIV2_ENABLE_BUILD_PO=OFF -DEXIV2_ENABLE_NLS=OFF -DICONV_INCLUDE_DIR=${PREFIX_ext_exiv2}/include + INSTALL_DIR ${PREFIX_ext_exiv2} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_exiv2} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DEXIV2_ENABLE_BUILD_SAMPLES=OFF -DEXIV2_ENABLE_BUILD_PO=OFF -DEXIV2_ENABLE_NLS=OFF -DICONV_INCLUDE_DIR=${PREFIX_ext_exiv2}/include - UPDATE_COMMAND "" - DEPENDS ext_iconv ext_expat + UPDATE_COMMAND "" + DEPENDS ext_iconv ext_expat ) +endif() diff --git a/3rdparty/ext_qt/0001-adapted-to-changed-C-API.patch b/3rdparty/ext_qt/0001-adapted-to-changed-C-API.patch new file mode 100644 index 0000000000..1aec81ce40 --- /dev/null +++ b/3rdparty/ext_qt/0001-adapted-to-changed-C-API.patch @@ -0,0 +1,25 @@ +From 7edf7f22415082303eb6df2c0502152e40cc5135 Mon Sep 17 00:00:00 2001 +From: Dirk Farin +Date: Fri, 25 May 2018 15:59:52 +0200 +Subject: [PATCH 1/3] adapted to changed C++ API + +--- + plugins/impex/heif/HeifExport.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/plugins/impex/heif/HeifExport.cpp b/plugins/impex/heif/HeifExport.cpp +index 3c9fae1736..2f4629fc95 100644 +--- a/plugins/impex/heif/HeifExport.cpp ++++ b/plugins/impex/heif/HeifExport.cpp +@@ -93,7 +93,7 @@ public: + { + } + +- heif_error write(heif::Context&, const void* data, size_t size) override { ++ heif_error write(const void* data, size_t size) override { + qint64 n = m_io->write((const char*)data,size); + if (n != (qint64)size) { + QString error = m_io->errorString(); +-- +2.14.1 + diff --git a/libs/widgets/KoGenericRegistryModel.h b/interfaces/KoGenericRegistryModel.h similarity index 100% rename from libs/widgets/KoGenericRegistryModel.h rename to interfaces/KoGenericRegistryModel.h diff --git a/krita/krita.action b/krita/krita.action index 87c6cb96d4..d247f8d089 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -1,3467 +1,3603 @@ General Open Resources Folder Opens a file browser at the location Krita saves resources such as brushes to. Opens a file browser at the location Krita saves resources such as brushes to. Open Resources Folder 0 0 false Cleanup removed files... Cleanup removed files Cleanup removed files 0 0 false C&ascade Cascade Cascade 10 0 false &Tile Tile Tile 10 0 false Create Resource Bundle... Create Resource Bundle Create Resource Bundle 0 0 false Show File Toolbar Show File Toolbar Show File Toolbar false Show color selector Show color selector Show color selector Shift+I false Show MyPaint shade selector Show MyPaint shade selector Show MyPaint shade selector Shift+M false Show minimal shade selector Show minimal shade selector Show minimal shade selector Shift+N false Show color history Show color history Show color history H false Show common colors Show common colors Show common colors U false Show Tool Options Show Tool Options Show Tool Options \ false Show Brush Editor Show Brush Editor Show Brush Editor F5 false Show Brush Presets Show Brush Presets Show Brush Presets F6 false Toggle Tablet Debugger Toggle Tablet Debugger Toggle Tablet Debugger 0 0 Ctrl+Shift+T false Show system information for bug reports. Show system information for bug reports. Show system information for bug reports. false Rename Composition... Rename Composition Rename Composition 0 0 false Update Composition Update Composition Update Composition 0 0 false Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale 1 0 true &Invert Selection Invert current selection Invert Selection 10000000000 100 Ctrl+Shift+I false Painting lightness-increase Make brush color lighter Make brush color lighter Make brush color lighter 0 0 L false lightness-decrease Make brush color darker Make brush color darker Make brush color darker 0 0 K false Make brush color more saturated Make brush color more saturated Make brush color more saturated false Make brush color more desaturated Make brush color more desaturated Make brush color more desaturated false Shift brush color hue clockwise Shift brush color hue clockwise Shift brush color hue clockwise false Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise false Make brush color more red Make brush color more red Make brush color more red false Make brush color more green Make brush color more green Make brush color more green false Make brush color more blue Make brush color more blue Make brush color more blue false Make brush color more yellow Make brush color more yellow Make brush color more yellow false opacity-increase Increase opacity Increase opacity Increase opacity 0 0 O false opacity-decrease Decrease opacity Decrease opacity Decrease opacity 0 0 I false draw-eraser Set eraser mode Set eraser mode Set eraser mode 10000 0 E true view-refresh Reload Original Preset Reload Original Preset Reload Original Preset 10000 false transparency-unlocked Preserve Alpha Preserve Alpha Preserve Alpha 10000 true transform_icons_penPressure Use Pen Pressure Use Pen Pressure Use Pen Pressure 10000 true symmetry-horizontal Horizontal Mirror Tool Horizontal Mirror Tool Horizontal Mirror Tool 0 true symmetry-vertical Vertical Mirror Tool Vertical Mirror Tool Vertical Mirror Tool 0 true Hide Mirror X Line Hide Mirror X Line Hide Mirror X Line 10000 true Hide Mirror Y Line Hide Mirror Y Line Hide Mirror Y Line 10000 true Lock Lock X Line Lock X Line 10000 true Lock Y Line Lock Y Line Lock Y Line 10000 true Move to Canvas Center Move to Canvas Center X Move to Canvas Center X 10000 false Move to Canvas Center Y Move to Canvas Center Y Move to Canvas Center Y 10000 false &Toggle Selection Display Mode Toggle Selection Display Mode Toggle Selection Display Mode 0 0 false Next Favourite Preset Next Favourite Preset Next Favourite Preset , false Previous Favourite Preset Previous Favourite Preset Previous Favourite Preset . false preset-switcher Switch to Previous Preset Switch to Previous Preset Switch to Previous Preset / false Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar true Reset Foreground and Background Color Reset Foreground and Background Color Reset Foreground and Background Color D false Swap Foreground and Background Color Swap Foreground and Background Color Swap Foreground and Background Color X false + + + Selection Mode: Add + + Selection Mode: Add + Selection Mode: Add + A + false + + + + + Selection Mode: Subtract + + Selection Mode: Subtract + Selection Mode: Subtract + S + false + + + + + Selection Mode: Intersect + + Selection Mode: Intersect + Selection Mode: Intersect + + false + + + + + Selection Mode: Replace + + Selection Mode: Replace + Selection Mode: Replace + R + false + + smoothing-weighted Brush Smoothing: Weighted Brush Smoothing: Weighted Brush Smoothing: Weighted false smoothing-no Brush Smoothing: Disabled Brush Smoothing: Disabled Brush Smoothing: Disabled false smoothing-stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer false brushsize-decrease Decrease Brush Size Decrease Brush Size Decrease Brush Size 0 0 [ false smoothing-basic Brush Smoothing: Basic Brush Smoothing: Basic Brush Smoothing: Basic false brushsize-increase Increase Brush Size Increase Brush Size Increase Brush Size 0 0 ] false Toggle 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 &Show Global Selection Mask Shows global selection as a usual selection mask in <interface>Layers</interface> docker Show Global Selection Mask 100000 100 true Filters color-to-alpha &Color to Alpha... Color to Alpha Color to Alpha 10000 0 false &Top Edge Detection Top Edge Detection Top Edge Detection 10000 0 false &Index Colors... Index Colors Index Colors 10000 0 false Emboss Horizontal &Only Emboss Horizontal Only Emboss Horizontal Only 10000 0 false D&odge Dodge Dodge 10000 0 false &Sharpen Sharpen Sharpen 10000 0 false B&urn Burn Burn 10000 0 false &Mean Removal Mean Removal Mean Removal 10000 0 false &Gaussian Blur... Gaussian Blur Gaussian Blur 10000 0 false Emboss &in All Directions Emboss in All Directions Emboss in All Directions 10000 0 false &Small Tiles... Small Tiles Small Tiles 10000 0 false &Levels... Levels Levels 10000 0 Ctrl+L false &Sobel... Sobel Sobel 10000 0 false &Wave... Wave Wave 10000 0 false &Motion Blur... Motion Blur Motion Blur 10000 0 false &Invert Invert Invert 10000 0 Ctrl+I false &Color Adjustment curves... Color Adjustment curves Color Adjustment curves 10000 0 Ctrl+M false Pi&xelize... Pixelize Pixelize 10000 0 false Emboss (&Laplacian) Emboss (Laplacian) Emboss (Laplacian) 10000 0 false &Left Edge Detection Left Edge Detection Left Edge Detection 10000 0 false &Blur... Blur Blur 10000 0 false &Raindrops... Raindrops Raindrops 10000 0 false &Bottom Edge Detection Bottom Edge Detection Bottom Edge Detection 10000 0 false &Random Noise... Random Noise Random Noise 10000 0 false &Brightness/Contrast curve... Brightness/Contrast curve Brightness/Contrast curve 10000 0 false Colo&r Balance... Color Balance Color Balance 10000 0 Ctrl+B false &Phong Bumpmap... Phong Bumpmap Phong Bumpmap 10000 0 false &Desaturate Desaturate Desaturate 10000 0 Ctrl+Shift+U false Color &Transfer... Color Transfer Color Transfer 10000 0 false Emboss &Vertical Only Emboss Vertical Only Emboss Vertical Only 10000 0 false &Lens Blur... Lens Blur Lens Blur 10000 0 false M&inimize Channel Minimize Channel Minimize Channel 10000 0 false M&aximize Channel Maximize Channel Maximize Channel 10000 0 false &Oilpaint... Oilpaint Oilpaint 10000 0 false &Right Edge Detection Right Edge Detection Right Edge Detection 10000 0 false &Auto Contrast Auto Contrast Auto Contrast 10000 0 false &Round Corners... Round Corners Round Corners 10000 0 false &Unsharp Mask... Unsharp Mask Unsharp Mask 10000 0 false &Emboss with Variable Depth... Emboss with Variable Depth Emboss with Variable Depth 10000 0 false Emboss &Horizontal && Vertical Emboss Horizontal & Vertical Emboss Horizontal & Vertical 10000 0 false Random &Pick... Random Pick Random Pick 10000 0 false &Gaussian Noise Reduction... Gaussian Noise Reduction Gaussian Noise Reduction 10000 0 false &Posterize... Posterize Posterize 10000 0 false &Wavelet Noise Reducer... Wavelet Noise Reducer Wavelet Noise Reducer 10000 0 false &HSV Adjustment... HSV Adjustment HSV Adjustment 10000 0 Ctrl+U false Tool Shortcuts Dynamic Brush Tool Dynamic Brush Tool Dynamic Brush Tool false 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 Rectangle Tool Rectangle Tool Rectangle Tool false Multibrush Tool Multibrush Tool Multibrush Tool Q false Colorize Mask Tool Colorize Mask Tool Colorize Mask Tool Smart Patch Tool Smart Patch Tool Smart Patch Tool Pan Tool Pan Tool Pan Tool Select Shapes Tool Select Shapes Tool Select Shapes Tool false Color Picker Select a color from the image or current layer Select a color from the image or current layer P false Outline Selection Tool Outline Selection Tool Outline Selection Tool 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 or double-click ends the curve. Bezier Curve Tool. Shift-mouseclick or double-click 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 Zoom Tool Zoom Tool Zoom Tool 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 Assistant Tool Assistant Tool Assistant Tool false Text tool Text tool Text tool false Gradient Editing Tool Gradient editing Gradient editing false Reference Images Tool Reference Images Tool Reference Images Tool false Blending Modes Select Normal Blending Mode Select Normal Blending Mode Select Normal Blending Mode 0 0 Alt+Shift+N false Select Dissolve Blending Mode Select Dissolve Blending Mode Select Dissolve Blending Mode 0 0 Alt+Shift+I false Select Behind Blending Mode Select Behind Blending Mode Select Behind Blending Mode 0 0 Alt+Shift+Q false Select Clear Blending Mode Select Clear Blending Mode Select Clear Blending Mode 0 0 Alt+Shift+R false Select Darken Blending Mode Select Darken Blending Mode Select Darken Blending Mode 0 0 Alt+Shift+K false Select Multiply Blending Mode Select Multiply Blending Mode Select Multiply Blending Mode 0 0 Alt+Shift+M false Select Color Burn Blending Mode Select Color Burn Blending Mode Select Color Burn Blending Mode 0 0 Alt+Shift+B false Select Linear Burn Blending Mode Select Linear Burn Blending Mode Select Linear Burn Blending Mode 0 0 Alt+Shift+A false Select Lighten Blending Mode Select Lighten Blending Mode Select Lighten Blending Mode 0 0 Alt+Shift+G false Select Screen Blending Mode Select Screen Blending Mode Select Screen Blending Mode 0 0 Alt+Shift+S false Select Color Dodge Blending Mode Select Color Dodge Blending Mode Select Color Dodge Blending Mode 0 0 Alt+Shift+D false Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode 0 0 Alt+Shift+W false Select Overlay Blending Mode Select Overlay Blending Mode Select Overlay Blending Mode 0 0 Alt+Shift+O false Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode 0 0 Alt+Shift+P false Select Soft Light Blending Mode Select Soft Light Blending Mode Select Soft Light Blending Mode 0 0 Alt+Shift+F false Select Hard Light Blending Mode Select Hard Light Blending Mode Select Hard Light Blending Mode 0 0 Alt+Shift+H false Select Vivid Light Blending Mode Select Vivid Light Blending Mode Select Vivid Light Blending Mode 0 0 Alt+Shift+V false Select Linear Light Blending Mode Select Linear Light Blending Mode Select Linear Light Blending Mode 0 0 Alt+Shift+J false Select Pin Light Blending Mode Select Pin Light Blending Mode Select Pin Light Blending Mode 0 0 Alt+Shift+Z false Select Hard Mix Blending Mode Select Hard Mix Blending Mode Select Hard Mix Blending Mode 0 0 Alt+Shift+L false Select Difference Blending Mode Select Difference Blending Mode Select Difference Blending Mode 0 0 Alt+Shift+E false Select Exclusion Blending Mode Select Exclusion Blending Mode Select Exclusion Blending Mode 0 0 Alt+Shift+X false Select Hue Blending Mode Select Hue Blending Mode Select Hue Blending Mode 0 0 Alt+Shift+U false Select Saturation Blending Mode Select Saturation Blending Mode Select Saturation Blending Mode 0 0 Alt+Shift+T false Select Color Blending Mode Select Color Blending Mode Select Color Blending Mode 0 0 Alt+Shift+C false Select Luminosity Blending Mode Select Luminosity Blending Mode Select Luminosity Blending Mode 0 0 Alt+Shift+Y false Animation 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 addblankframe Create Blank Frame Add blank frame Add blank frame 100000 0 false addduplicateframe Create Duplicate Frame Add duplicate frame Add duplicate frame 100000 0 false Toggle onion skin Toggle onion skin Toggle onion skin 100000 0 false Previous Keyframe false Next Keyframe false First Frame false Last Frame false Auto Frame Mode true true Show in Timeline true Insert Keyframe Left Insert keyframes to the left of selection, moving the tail of animation to the right. 100000 0 false Insert Keyframe Right Insert keyframes to the right of selection, moving the tail of animation to the right. 100000 0 false Insert Multiple Keyframes Insert several keyframes based on user parameters. 100000 0 false Remove Frame and Pull Remove keyframes moving the tail of animation to the left 100000 0 false deletekeyframe Remove Keyframe Remove keyframes without moving anything around 100000 0 false Insert Column Left Insert column to the left of selection, moving the tail of animation to the right 100000 0 false Insert Column Right Insert column to the right of selection, moving the tail of animation to the right 100000 0 false Insert Multiple Columns Insert several columns based on user parameters. 100000 0 false Remove Column and Pull Remove columns moving the tail of animation to the left 100000 0 false Remove Column Remove columns without moving anything around 100000 0 false Insert Hold Frame Insert a hold frame after every keyframe 100000 0 false Insert Multiple Hold Frames Insert N hold frames after every keyframe 100000 0 false Remove Hold Frame Remove a hold frame after every keyframe 100000 0 false Remove Multiple Hold Frames Remove N hold frames after every keyframe 100000 0 false Insert Hold Column Insert a hold column into the frame at the current position 100000 0 false Insert Multiple Hold Columns Insert N hold columns into the frame at the current position 100000 0 false Remove Hold Column Remove a hold column from the frame at the current position 100000 0 false Remove Multiple Hold Columns Remove N hold columns from the frame at the current position 100000 0 false Mirror Frames Mirror frames' position 100000 0 false Mirror Columns Mirror columns' position 100000 0 false Copy to Clipboard Copy frames to clipboard 100000 0 false Cut to Clipboard Cut frames to clipboard 100000 0 false Paste from Clipboard Paste frames from clipboard 100000 0 false Copy Columns to Clipboard Copy columns to clipboard 100000 0 false Cut Columns to Clipboard Cut columns to clipboard 100000 0 false Paste Columns from Clipboard Paste columns from clipboard 100000 0 false Set Start Time 100000 0 false Set End Time 100000 0 false Update Playback Range 100000 0 false Layers Activate next layer Activate next layer Activate next layer 1000 0 PgUp false Activate previous layer Activate previous layer Activate previous layer 1000 0 PgDown false Activate previously selected layer Activate previously selected layer Activate previously selected layer 1000 0 ; false groupLayer &Group Layer Group Layer Group Layer 1000 0 false cloneLayer &Clone Layer Clone Layer Clone Layer 1000 0 false vectorLayer &Vector Layer Vector Layer Vector Layer 1000 0 false filterLayer &Filter Layer... Filter Layer Filter Layer 1000 0 false fillLayer &Fill Layer... Fill Layer Fill Layer 1000 0 false fileLayer &File Layer... File Layer File Layer 1000 0 false transparencyMask &Transparency Mask Transparency Mask Transparency Mask 100000 0 false filterMask &Filter Mask... Filter Mask Filter Mask 100000 0 false filterMask &Colorize Mask Colorize Mask Colorize Mask 100000 0 false transformMask &Transform Mask... Transform Mask Transform Mask 100000 0 false selectionMask &Local Selection Local Selection Local Selection 100000 0 false view-filter &Isolate Layer Isolate Layer Isolate Layer 1000 0 true layer-locked &Toggle layer lock Toggle layer lock Toggle layer lock 1000 0 false visible Toggle layer &visibility Toggle layer visibility Toggle layer visibility 1000 0 false transparency-locked Toggle layer &alpha Toggle layer alpha Toggle layer alpha 1000 0 false transparency-enabled Toggle layer alpha &inheritance Toggle layer alpha inheritance Toggle layer alpha inheritance 1000 0 false paintLayer &Paint Layer Paint Layer Paint Layer 1000 0 Insert false &New Layer From Visible New layer from visible New layer from visible 1000 0 false duplicatelayer &Duplicate Layer or Mask Duplicate Layer or Mask Duplicate Layer or Mask 1000 0 Ctrl+J false &Cut Selection to New Layer Cut Selection to New Layer Cut Selection to New Layer 100000000 1 Ctrl+Shift+J false Copy &Selection to New Layer Copy Selection to New Layer Copy Selection to New Layer 100000000 0 Ctrl+Alt+J false Copy Layer Copy layer to clipboard Copy layer to clipboard 1000 0 false Cut Layer Cut layer to clipboard Cut layer to clipboard 1000 0 false Paste Layer Paste layer from clipboard Paste layer from clipboard 1000 0 false Quick Group Create a group layer containing selected layers Quick Group 1000 0 Ctrl+G false Quick Ungroup Remove grouping of the layers or remove one layer out of the group Quick Ungroup 100000 0 Ctrl+Alt+G false Quick Clipping Group Group selected layers and add a layer with clipped alpha channel Quick Clipping Group 100000 0 Ctrl+Shift+G false All Layers Select all layers Select all layers 10000 0 false Visible Layers Select all visible layers Select all visible layers 10000 0 false Locked Layers Select all locked layers Select all locked layers 10000 0 false Invisible Layers Select all invisible layers Select all invisible layers 10000 0 false Unlocked Layers Select all unlocked layers Select all unlocked layers 10000 0 false document-save &Save Layer/Mask... Save Layer/Mask Save Layer/Mask 1000 0 false document-save Save Vector Layer as SVG... Save Vector Layer as SVG Save Vector Layer as SVG 1000 0 false document-save Save &Group Layers... Save Group Layers Save Group Layers 100000 0 false Convert group to &animated layer Convert child layers into animation frames Convert child layers into animation frames 100000 0 false fileLayer to &File Layer Saves out the layers into a new image and then references that image. Convert to File Layer 100000 0 false I&mport Layer... Import Layer Import Layer 100000 0 false paintLayer &as Paint Layer... as Paint Layer as Paint Layer 1000 0 false transparencyMask as &Transparency Mask... as Transparency Mask as Transparency Mask 1000 0 false filterMask as &Filter Mask... as Filter Mask as Filter Mask 1000 0 false selectionMask as &Selection Mask... as Selection Mask as Selection Mask 1000 0 false paintLayer to &Paint Layer to Paint Layer to Paint Layer 1000 0 false transparencyMask to &Transparency Mask to Transparency Mask to Transparency Mask 1000 0 false filterMask to &Filter Mask... to Filter Mask to Filter Mask 1000 0 false selectionMask to &Selection Mask to Selection Mask to Selection Mask 1000 0 false transparencyMask &Alpha into Mask Alpha into Mask Alpha into Mask 100000 10 false transparency-enabled &Write as Alpha Write as Alpha Write as Alpha 1000000 1 false document-save &Save Merged... Save Merged Save Merged 1000000 0 false split-layer Split Layer... Split Layer Split Layer 1000 0 false Wavelet Decompose ... Wavelet Decompose Wavelet Decompose 1000 1 false symmetry-horizontal Mirror Layer Hori&zontally Mirror Layer Horizontally Mirror Layer Horizontally 1000 1 false symmetry-vertical Mirror Layer &Vertically Mirror Layer Vertically Mirror Layer Vertically 1000 1 false &Rotate Layer... Rotate Layer Rotate Layer 1000 1 false object-rotate-right Rotate &Layer 90° to the Right Rotate Layer 90° to the Right Rotate Layer 90° to the Right 1000 1 false object-rotate-left Rotate Layer &90° to the Left Rotate Layer 90° to the Left Rotate Layer 90° to the Left 1000 1 false Rotate Layer &180° Rotate Layer 180° Rotate Layer 180° 1000 1 false Scale &Layer to new Size... Scale Layer to new Size Scale Layer to new Size 100000 1 false &Shear Layer... Shear Layer Shear Layer 1000 1 false + + symmetry-horizontal + Mirror All Layers Hori&zontally + + Mirror All Layers Horizontally + Mirror All Layers Horizontally + 1000 + 1 + + false + + + + symmetry-vertical + Mirror All Layers &Vertically + + Mirror All Layers Vertically + Mirror All Layers Vertically + 1000 + 1 + + false + + + + + &Rotate All Layers... + + Rotate All Layers + Rotate All Layers + 1000 + 1 + + false + + + + object-rotate-right + Rotate All &Layers 90° to the Right + + Rotate All Layers 90° to the Right + Rotate All Layers 90° to the Right + 1000 + 1 + + false + + + + object-rotate-left + Rotate All Layers &90° to the Left + + Rotate All Layers 90° to the Left + Rotate All Layers 90° to the Left + 1000 + 1 + + false + + + + + Rotate All Layers &180° + + Rotate All Layers 180° + Rotate All Layers 180° + 1000 + 1 + + false + + + + + Scale All &Layers to new Size... + + Scale All Layers to new Size + Scale All Layers to new Size + 100000 + 1 + + false + + + + + &Shear All Layers... + + Shear All Layers + Shear All Layers + 1000 + 1 + + false + + &Offset Layer... Offset Layer Offset Layer 100000 1 false Clones &Array... Clones Array Clones Array 100000 0 false &Edit metadata... Edit metadata Edit metadata 100000 1 false &Histogram... Histogram Histogram 100000 0 false &Convert Layer Color Space... Convert Layer Color Space Convert Layer Color Space 100000 1 false merge-layer-below &Merge with Layer Below Merge with Layer Below Merge with Layer Below 100000 0 Ctrl+E false &Flatten Layer Flatten Layer Flatten Layer 100000 0 false Ras&terize Layer Rasterize Layer Rasterize Layer 10000000 1 false Flatten ima&ge Flatten image Flatten image 100000 0 Ctrl+Shift+E false La&yer Style... Layer Style Layer Style 100000 1 false Move into previous group Move into previous group Move into previous group 0 0 false Move into next group Move into next group Move into next group 0 0 false Rename current layer Rename current layer Rename current layer 100000 0 F2 false deletelayer &Remove Layer Remove Layer Remove Layer 1000 1 Shift+Delete false arrowupblr Move Layer or Mask Up Move Layer or Mask Up Ctrl+PgUp false arrowdown Move Layer or Mask Down Move Layer or Mask Down Ctrl+PgDown false properties &Properties... Properties Properties 1000 1 F3 false diff --git a/krita/krita4.xmlgui b/krita/krita4.xmlgui index d050f12037..5e6da25a83 100644 --- a/krita/krita4.xmlgui +++ b/krita/krita4.xmlgui @@ -1,389 +1,404 @@ &File &Edit Fill Special &View &Canvas &Snap To &Image &Rotate &Layer New &Import/Export Import &Convert &Select &Group &Transform &Rotate + + Transform &All Layers + + + + + &Rotate + + + + + + + + S&plit S&plit Alpha &Select Select &Opaque Filte&r &Tools Scripts Setti&ngs &Help File Brushes and Stuff diff --git a/krita/kritamenu.action b/krita/kritamenu.action index efc698bd37..9a2ccf7ebe 100644 --- a/krita/kritamenu.action +++ b/krita/kritamenu.action @@ -1,1830 +1,1830 @@ 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 Sessions... Open session manager Sessions 0 0 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 at Cursor Paste at cursor Paste at cursor 0 0 Ctrl+Alt+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 Fill Special 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 Stro&ke selected shapes Stroke selected shapes Stroke selected shapes 1000000000 0 false 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 true &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 Mirror View M false zoom-original &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 Rotate Canvas Right 1 0 Ctrl+] false rotate-canvas-left Rotate Canvas &Left Rotate Canvas Left Rotate Canvas Left 1 0 Ctrl+[ false rotation-reset 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 Show Pixel Grid Show Pixel Grid Show Pixel Grid 1000 - 0 + 1000 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 S&how Reference Images Show Reference Images Show Reference Images 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 Selection Invert Selection Invert Selection 10000 0 Ctrl+I false &Convert to Vector Selection Convert to Vector Selection Convert to Vector Selection 100000000000000000 0 false &Convert to Raster Selection Convert to Raster Selection Convert to Raster Selection 10000000000000000 0 false Edit Selection Edit Selection Edit Selection 10000000000 100 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 (Replace) Select Opaque Select Opaque 10000 100 false Select Opaque (&Add) Select Opaque (Add) Select Opaque (Add) 10000 100 false Select Opaque (&Subtract) Select Opaque (Subtract) Select Opaque (Subtract) 10000 100 false Select Opaque (&Intersect) Select Opaque (Intersect) Select Opaque (Intersect) 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 gmic Start G'MIC-Qt Start G'Mic-Qt Start G'Mic-Qt false gmic Re-apply the last G'MIC filter Apply the last G'Mic-Qt action again Apply the last G'Mic-Qt action again 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 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 Layouts Select layout false Workspaces Workspaces Workspaces false diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index dca1ba7a86..c74d679c41 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -1,23 +1,24 @@ add_subdirectory( version ) add_subdirectory( global ) add_subdirectory( koplugin ) add_subdirectory( widgetutils ) add_subdirectory( widgets ) add_subdirectory( store ) add_subdirectory( odf ) add_subdirectory( flake ) add_subdirectory( basicflakes ) add_subdirectory( pigment ) add_subdirectory( command ) add_subdirectory( brush ) add_subdirectory( psd ) add_subdirectory( color ) add_subdirectory( image ) add_subdirectory( ui ) add_subdirectory( vectorimage ) add_subdirectory( impex ) add_subdirectory( libkis ) if (NOT APPLE AND HAVE_QT_QUICK) add_subdirectory( libqml ) endif() +add_subdirectory( metadata ) diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt index 06d757f161..23828dfb88 100644 --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -1,379 +1,366 @@ add_subdirectory( tests ) add_subdirectory( tiles3 ) include_directories( ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/metadata ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty ${CMAKE_CURRENT_SOURCE_DIR}/brushengine ${CMAKE_CURRENT_SOURCE_DIR}/commands ${CMAKE_CURRENT_SOURCE_DIR}/commands_new ${CMAKE_CURRENT_SOURCE_DIR}/filter ${CMAKE_CURRENT_SOURCE_DIR}/floodfill ${CMAKE_CURRENT_SOURCE_DIR}/generator ${CMAKE_CURRENT_SOURCE_DIR}/layerstyles ${CMAKE_CURRENT_SOURCE_DIR}/processing ${CMAKE_SOURCE_DIR}/sdk/tests ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(FFTW3_FOUND) include_directories(${FFTW3_INCLUDE_DIR}) endif() if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR} ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) ko_compile_for_all_implementations(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp) else() set(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp) endif() set(kritaimage_LIB_SRCS tiles3/kis_tile.cc tiles3/kis_tile_data.cc tiles3/kis_tile_data_store.cc tiles3/kis_tile_data_pooler.cc tiles3/kis_tiled_data_manager.cc tiles3/KisTiledExtentManager.cpp tiles3/kis_memento_manager.cc tiles3/kis_hline_iterator.cpp tiles3/kis_vline_iterator.cpp tiles3/kis_random_accessor.cc tiles3/swap/kis_abstract_compression.cpp tiles3/swap/kis_lzf_compression.cpp tiles3/swap/kis_abstract_tile_compressor.cpp tiles3/swap/kis_legacy_tile_compressor.cpp tiles3/swap/kis_tile_compressor_2.cpp tiles3/swap/kis_chunk_allocator.cpp tiles3/swap/kis_memory_window.cpp tiles3/swap/kis_swapped_data_store.cpp tiles3/swap/kis_tile_data_swapper.cpp kis_distance_information.cpp kis_painter.cc kis_painter_blt_multi_fixed.cpp kis_marker_painter.cpp KisPrecisePaintDeviceWrapper.cpp kis_progress_updater.cpp brushengine/kis_paint_information.cc brushengine/kis_random_source.cpp brushengine/KisPerStrokeRandomSource.cpp brushengine/kis_stroke_random_source.cpp brushengine/kis_paintop.cc brushengine/kis_paintop_factory.cpp brushengine/kis_paintop_preset.cpp brushengine/kis_paintop_registry.cc brushengine/kis_paintop_settings.cpp brushengine/kis_paintop_settings_update_proxy.cpp brushengine/kis_paintop_utils.cpp brushengine/kis_no_size_paintop_settings.cpp brushengine/kis_locked_properties.cc brushengine/kis_locked_properties_proxy.cpp brushengine/kis_locked_properties_server.cpp brushengine/kis_paintop_config_widget.cpp brushengine/kis_uniform_paintop_property.cpp brushengine/kis_combo_based_paintop_property.cpp brushengine/kis_slider_based_paintop_property.cpp brushengine/kis_standard_uniform_properties_factory.cpp brushengine/KisStrokeSpeedMeasurer.cpp brushengine/KisPaintopSettingsIds.cpp commands/kis_deselect_global_selection_command.cpp commands/KisDeselectActiveSelectionCommand.cpp commands/kis_image_change_layers_command.cpp commands/kis_image_change_visibility_command.cpp commands/kis_image_command.cpp commands/kis_image_set_projection_color_space_command.cpp commands/kis_image_layer_add_command.cpp commands/kis_image_layer_move_command.cpp commands/kis_image_layer_remove_command.cpp commands/kis_image_layer_remove_command_impl.cpp commands/kis_image_lock_command.cpp commands/kis_node_command.cpp commands/kis_node_compositeop_command.cpp commands/kis_node_opacity_command.cpp commands/kis_node_property_list_command.cpp commands/kis_reselect_global_selection_command.cpp commands/KisReselectActiveSelectionCommand.cpp commands/kis_set_global_selection_command.cpp commands_new/kis_saved_commands.cpp commands_new/kis_processing_command.cpp commands_new/kis_image_resize_command.cpp commands_new/kis_image_set_resolution_command.cpp commands_new/kis_node_move_command2.cpp commands_new/kis_set_layer_style_command.cpp commands_new/kis_selection_move_command2.cpp commands_new/kis_update_command.cpp commands_new/kis_switch_current_time_command.cpp commands_new/kis_change_projection_color_command.cpp commands_new/kis_activate_selection_mask_command.cpp + commands_new/kis_transaction_based_command.cpp processing/kis_do_nothing_processing_visitor.cpp processing/kis_simple_processing_visitor.cpp processing/kis_crop_processing_visitor.cpp processing/kis_crop_selections_processing_visitor.cpp processing/kis_transform_processing_visitor.cpp processing/kis_mirror_processing_visitor.cpp + processing/KisSelectionBasedProcessingHelper.cpp filter/kis_filter.cc filter/kis_filter_category_ids.cpp filter/kis_filter_configuration.cc filter/kis_color_transformation_configuration.cc filter/kis_filter_registry.cc filter/kis_color_transformation_filter.cc generator/kis_generator.cpp generator/kis_generator_layer.cpp generator/kis_generator_registry.cpp floodfill/kis_fill_interval_map.cpp floodfill/kis_scanline_fill.cpp lazybrush/kis_min_cut_worker.cpp lazybrush/kis_lazy_fill_tools.cpp lazybrush/kis_multiway_cut.cpp lazybrush/KisWatershedWorker.cpp lazybrush/kis_colorize_mask.cpp lazybrush/kis_colorize_stroke_strategy.cpp KisDelayedUpdateNodeInterface.cpp kis_adjustment_layer.cc kis_selection_based_layer.cpp kis_node_filter_interface.cpp kis_base_accessor.cpp kis_base_node.cpp kis_base_processor.cpp kis_bookmarked_configuration_manager.cc kis_node_uuid_info.cpp kis_clone_layer.cpp kis_colorspace_convert_visitor.cpp kis_config_widget.cpp kis_convolution_kernel.cc kis_convolution_painter.cc kis_gaussian_kernel.cpp kis_edge_detection_kernel.cpp kis_cubic_curve.cpp kis_default_bounds.cpp kis_default_bounds_base.cpp kis_effect_mask.cc kis_fast_math.cpp kis_fill_painter.cc kis_filter_mask.cpp kis_filter_strategy.cc kis_transform_mask.cpp kis_transform_mask_params_interface.cpp kis_recalculate_transform_mask_job.cpp kis_recalculate_generator_layer_job.cpp kis_transform_mask_params_factory_registry.cpp kis_safe_transform.cpp kis_gradient_painter.cc kis_gradient_shape_strategy.cpp kis_cached_gradient_shape_strategy.cpp kis_polygonal_gradient_shape_strategy.cpp kis_iterator_ng.cpp kis_async_merger.cpp kis_merge_walker.cc kis_updater_context.cpp kis_update_job_item.cpp kis_stroke_strategy_undo_command_based.cpp kis_simple_stroke_strategy.cpp KisRunnableBasedStrokeStrategy.cpp KisRunnableStrokeJobData.cpp KisRunnableStrokeJobsInterface.cpp KisFakeRunnableStrokeJobsExecutor.cpp kis_stroke_job_strategy.cpp kis_stroke_strategy.cpp kis_stroke.cpp kis_strokes_queue.cpp KisStrokesQueueMutatedJobInterface.cpp kis_simple_update_queue.cpp kis_update_scheduler.cpp kis_queues_progress_updater.cpp kis_composite_progress_proxy.cpp kis_sync_lod_cache_stroke_strategy.cpp kis_lod_capable_layer_offset.cpp kis_update_time_monitor.cpp KisImageConfigNotifier.cpp kis_group_layer.cc kis_count_visitor.cpp kis_histogram.cc kis_image_interfaces.cpp kis_image_animation_interface.cpp kis_time_range.cpp kis_node_graph_listener.cpp kis_image.cc kis_image_signal_router.cpp kis_image_config.cpp kis_projection_updates_filter.cpp kis_suspend_projection_updates_stroke_strategy.cpp kis_regenerate_frame_stroke_strategy.cpp kis_switch_time_stroke_strategy.cpp kis_crop_saved_extra_data.cpp kis_timed_signal_threshold.cpp kis_layer.cc kis_indirect_painting_support.cpp kis_abstract_projection_plane.cpp kis_layer_projection_plane.cpp kis_layer_utils.cpp kis_mask_projection_plane.cpp kis_projection_leaf.cpp kis_mask.cc kis_base_mask_generator.cpp kis_rect_mask_generator.cpp kis_circle_mask_generator.cpp kis_gauss_circle_mask_generator.cpp kis_gauss_rect_mask_generator.cpp ${__per_arch_circle_mask_generator_objs} kis_curve_circle_mask_generator.cpp kis_curve_rect_mask_generator.cpp kis_math_toolbox.cpp kis_memory_statistics_server.cpp kis_name_server.cpp kis_node.cpp kis_node_facade.cpp kis_node_progress_proxy.cpp kis_busy_progress_indicator.cpp kis_node_visitor.cpp kis_paint_device.cc kis_paint_device_debug_utils.cpp kis_fixed_paint_device.cpp KisOptimizedByteArray.cpp kis_paint_layer.cc kis_perspective_math.cpp kis_pixel_selection.cpp kis_processing_information.cpp kis_properties_configuration.cc kis_random_accessor_ng.cpp kis_random_generator.cc kis_random_sub_accessor.cpp kis_wrapped_random_accessor.cpp kis_selection.cc KisSelectionUpdateCompressor.cpp kis_selection_mask.cpp kis_update_outline_job.cpp kis_update_selection_job.cpp kis_serializable_configuration.cc kis_transaction_data.cpp kis_transform_worker.cc kis_perspectivetransform_worker.cpp bsplines/kis_bspline_1d.cpp bsplines/kis_bspline_2d.cpp bsplines/kis_nu_bspline_2d.cpp kis_warptransform_worker.cc kis_cage_transform_worker.cpp kis_liquify_transform_worker.cpp kis_green_coordinates_math.cpp kis_transparency_mask.cc kis_undo_adapter.cpp kis_macro_based_undo_store.cpp kis_surrogate_undo_adapter.cpp kis_legacy_undo_adapter.cpp kis_post_execution_undo_adapter.cpp kis_processing_visitor.cpp kis_processing_applicator.cpp krita_utils.cpp kis_outline_generator.cpp kis_layer_composition.cpp kis_selection_filters.cpp KisProofingConfiguration.h - metadata/kis_meta_data_entry.cc - metadata/kis_meta_data_filter.cc - metadata/kis_meta_data_filter_p.cc - metadata/kis_meta_data_filter_registry.cc - metadata/kis_meta_data_filter_registry_model.cc - metadata/kis_meta_data_io_backend.cc - metadata/kis_meta_data_merge_strategy.cc - metadata/kis_meta_data_merge_strategy_p.cc - metadata/kis_meta_data_merge_strategy_registry.cc - metadata/kis_meta_data_parser.cc - metadata/kis_meta_data_schema.cc - metadata/kis_meta_data_schema_registry.cc - metadata/kis_meta_data_store.cc - metadata/kis_meta_data_type_info.cc - metadata/kis_meta_data_validator.cc - metadata/kis_meta_data_value.cc kis_keyframe.cpp kis_keyframe_channel.cpp kis_keyframe_commands.cpp kis_scalar_keyframe_channel.cpp kis_raster_keyframe_channel.cpp kis_onion_skin_compositor.cpp kis_onion_skin_cache.cpp kis_idle_watcher.cpp kis_psd_layer_style.cpp kis_layer_properties_icons.cpp layerstyles/kis_multiple_projection.cpp layerstyles/kis_layer_style_filter.cpp layerstyles/kis_layer_style_filter_environment.cpp layerstyles/kis_layer_style_filter_projection_plane.cpp layerstyles/kis_layer_style_projection_plane.cpp layerstyles/kis_ls_drop_shadow_filter.cpp layerstyles/kis_ls_satin_filter.cpp layerstyles/kis_ls_stroke_filter.cpp layerstyles/kis_ls_bevel_emboss_filter.cpp layerstyles/kis_ls_overlay_filter.cpp layerstyles/kis_ls_utils.cpp layerstyles/gimp_bump_map.cpp KisProofingConfiguration.cpp kis_node_query_path.cc ) set(einspline_SRCS 3rdparty/einspline/bspline_create.cpp 3rdparty/einspline/bspline_data.cpp 3rdparty/einspline/multi_bspline_create.cpp 3rdparty/einspline/nubasis.cpp 3rdparty/einspline/nubspline_create.cpp 3rdparty/einspline/nugrid.cpp ) add_library(kritaimage SHARED ${kritaimage_LIB_SRCS} ${einspline_SRCS}) generate_export_header(kritaimage BASE_NAME kritaimage) target_link_libraries(kritaimage PUBLIC kritaversion kritawidgets - kritaglobal kritapsd - kritaodf kritapigment + kritaglobal + kritapsd + kritaodf + kritapigment kritacommand kritawidgetutils + kritametadata Qt5::Concurrent ) target_link_libraries(kritaimage PUBLIC ${Boost_SYSTEM_LIBRARY}) if(OPENEXR_FOUND) target_link_libraries(kritaimage PUBLIC ${OPENEXR_LIBRARIES}) endif() if(FFTW3_FOUND) target_link_libraries(kritaimage PRIVATE ${FFTW3_LIBRARIES}) endif() if(HAVE_VC) target_link_libraries(kritaimage PUBLIC ${Vc_LIBRARIES}) endif() if (NOT GSL_FOUND) message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Shaped Gradients might be non-normalized! Please install GSL library.") else () target_link_libraries(kritaimage PRIVATE ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif () target_include_directories(kritaimage PUBLIC $ $ $ $ - $ $ ) set_target_properties(kritaimage PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaimage ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/ui/kis_transaction_based_command.cpp b/libs/image/commands_new/kis_transaction_based_command.cpp similarity index 100% rename from libs/ui/kis_transaction_based_command.cpp rename to libs/image/commands_new/kis_transaction_based_command.cpp diff --git a/libs/ui/kis_transaction_based_command.h b/libs/image/commands_new/kis_transaction_based_command.h similarity index 92% rename from libs/ui/kis_transaction_based_command.h rename to libs/image/commands_new/kis_transaction_based_command.h index a47b1d40e0..8c381bb476 100644 --- a/libs/ui/kis_transaction_based_command.h +++ b/libs/image/commands_new/kis_transaction_based_command.h @@ -1,40 +1,40 @@ /* * 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. */ #ifndef KIS_TRANSACTION_BASED_COMMAND_H #define KIS_TRANSACTION_BASED_COMMAND_H -#include +#include #include -class KRITAUI_EXPORT KisTransactionBasedCommand : public KUndo2Command +class KRITAIMAGE_EXPORT KisTransactionBasedCommand : public KUndo2Command { public: KisTransactionBasedCommand(const KUndo2MagicString &text = KUndo2MagicString(), KUndo2Command *parent = 0); ~KisTransactionBasedCommand() override; void redo() override; void undo() override; protected: virtual KUndo2Command* paint() = 0; private: KUndo2Command *m_transactionData; }; #endif // KIS_TRANSACTION_BASED_COMMAND_H diff --git a/libs/image/metadata/kis_exif_info_visitor.h b/libs/image/kis_exif_info_visitor.h similarity index 96% rename from libs/image/metadata/kis_exif_info_visitor.h rename to libs/image/kis_exif_info_visitor.h index bfbb413e43..1d7647f5e4 100644 --- a/libs/image/metadata/kis_exif_info_visitor.h +++ b/libs/image/kis_exif_info_visitor.h @@ -1,105 +1,105 @@ /* * Copyright (c) 2005 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_EXIF_INFO_VISITOR_H #define KIS_EXIF_INFO_VISITOR_H #include -#include -#include +#include +#include #include #include /** * @brief The KisExifInfoVisitor class looks for a layer with metadata. * * If there is more than one layer with metadata, the metadata provided * by the visitor is the metadata associated with the last layer that * had metadata on it. Only use the metadata if only one layer with * metadata was found. * * The metadata pointer is OWNED by the layer. * */ class KisExifInfoVisitor : public KisNodeVisitor { public: KisExifInfoVisitor() { } bool visit(KisNode*) override { return true; } bool visit(KisCloneLayer*) override { return true; } bool visit(KisFilterMask*) override { return true; } bool visit(KisTransformMask*) override { return true; } bool visit(KisTransparencyMask*) override { return true; } bool visit(KisSelectionMask*) override { return true; } bool visit(KisColorizeMask*) override { return true; } bool visit(KisExternalLayer*) override { return true; } bool visit(KisGeneratorLayer*) override { return true; } bool visit(KisAdjustmentLayer*) override { return true; } bool visit(KisPaintLayer* layer) override { if (!layer->metaData()->empty()) { m_metaDataObjectsEncountered++; m_exifInfo = layer->metaData(); } return true; } bool visit(KisGroupLayer* layer) override { dbgMetaData << "Visiting on grouplayer" << layer->name() << ""; return visitAll(layer, true); } public: inline uint metaDataCount() { dbgImage << "number of layers with metadata" << m_metaDataObjectsEncountered; return m_metaDataObjectsEncountered; } inline KisMetaData::Store* exifInfo() { return m_exifInfo; } private: KisMetaData::Store *m_exifInfo {0}; int m_metaDataObjectsEncountered {0}; }; #endif diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index fbea2059cd..37b47fc1a4 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,1840 +1,1892 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image.h" #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorProfile.h" #include #include "KisProofingConfiguration.h" #include "kis_adjustment_layer.h" #include "kis_annotation.h" #include "kis_change_profile_visitor.h" #include "kis_colorspace_convert_visitor.h" #include "kis_count_visitor.h" #include "kis_filter_strategy.h" #include "kis_group_layer.h" #include "commands/kis_image_commands.h" #include "kis_layer.h" #include "kis_meta_data_merge_strategy_registry.h" #include "kis_name_server.h" #include "kis_paint_layer.h" #include "kis_projection_leaf.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_transaction.h" #include "kis_meta_data_merge_strategy.h" #include "kis_memory_statistics_server.h" #include "kis_image_config.h" #include "kis_update_scheduler.h" #include "kis_image_signal_router.h" #include "kis_image_animation_interface.h" #include "kis_stroke_strategy.h" #include "kis_simple_stroke_strategy.h" #include "kis_image_barrier_locker.h" #include "kis_undo_stores.h" #include "kis_legacy_undo_adapter.h" #include "kis_post_execution_undo_adapter.h" #include "kis_transform_worker.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_processing_visitor.h" #include "processing/kis_crop_selections_processing_visitor.h" #include "processing/kis_transform_processing_visitor.h" #include "commands_new/kis_image_resize_command.h" #include "commands_new/kis_image_set_resolution_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_composite_progress_proxy.h" #include "kis_layer_composition.h" #include "kis_wrapped_rect.h" #include "kis_crop_saved_extra_data.h" #include "kis_layer_utils.h" #include "kis_lod_transform.h" #include "kis_suspend_projection_updates_stroke_strategy.h" #include "kis_sync_lod_cache_stroke_strategy.h" #include "kis_projection_updates_filter.h" #include "kis_layer_projection_plane.h" #include "kis_update_time_monitor.h" #include #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) , signalRouter(_q) , animationInterface(_animationInterface) , scheduler(_q, _q) , axesCenter(QPointF(0.5, 0.5)) { { KisImageConfig cfg(true); if (cfg.enableProgressReporting()) { scheduler.setProgressProxy(&compositeProgressProxy); } // Each of these lambdas defines a new factory function. scheduler.setLod0ToNStrokeStrategyFactory( [=](bool forgettable) { return KisLodSyncPair( new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable), KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q))); }); scheduler.setSuspendUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true), KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q))); }); scheduler.setResumeUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false), KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q))); }); } connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged())); } ~KisImagePrivate() { /** * Stop animation interface. It may use the rootLayer. */ delete animationInterface; /** * First delete the nodes, while strokes * and undo are still alive */ rootLayer.clear(); } KisImage *q; quint32 lockCount = 0; bool lockedForReadOnly; qint32 width; qint32 height; double xres = 1.0; double yres = 1.0; const KoColorSpace * colorSpace; KisProofingConfigurationSP proofingConfig; KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here KisSelectionMaskSP targetOverlaySelectionMask; // the overlay switching stroke will try to switch into this mask KisSelectionMaskSP overlaySelectionMask; QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; QScopedPointer undoStore; KisLegacyUndoAdapter legacyUndoAdapter; KisPostExecutionUndoAdapter postExecutionUndoAdapter; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; 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))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); setObjectName(name); setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); } KisImage::~KisImage() { dbgImage << "deleting kisimage" << objectName(); /** * Request the tools to end currently running strokes */ waitForDone(); delete m_d; disconnect(); // in case Qt gets confused } 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))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); 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 || rhs.m_d->isolatedRootNode) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(newRoot, [&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); if (exactCopy) { node->setUuid(refNode->uuid()); } if (rhs.m_d->isolatedRootNode && rhs.m_d->isolatedRootNode == refNode) { m_d->isolatedRootNode = node; } }); } 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; /** * The overlay device is not inherited when cloning the image! */ if (rhs.m_d->overlaySelectionMask) { const QRect dirtyRect = rhs.m_d->overlaySelectionMask->extent(); m_d->rootLayer->setDirty(dirtyRect); } } 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())) { emit sigInternalStopIsolatedModeRequested(); } } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data())) { emit sigInternalStopIsolatedModeRequested(); } KisNodeGraphListener::aboutToRemoveANode(parent, index); SANITY_CHECK_LOCKED("aboutToRemoveANode"); m_d->signalRouter.emitAboutToRemoveANode(parent, index); } void KisImage::nodeChanged(KisNode* node) { KisNodeGraphListener::nodeChanged(node); requestStrokeEnd(); m_d->signalRouter.emitNodeChanged(node); } void KisImage::invalidateAllFrames() { invalidateFrames(KisTimeRange::infinite(0), QRect()); } void KisImage::setOverlaySelectionMask(KisSelectionMaskSP mask) { if (m_d->targetOverlaySelectionMask == mask) return; m_d->targetOverlaySelectionMask = mask; struct UpdateOverlaySelectionStroke : public KisSimpleStrokeStrategy { UpdateOverlaySelectionStroke(KisImageSP image) : KisSimpleStrokeStrategy("update-overlay-selection-mask", kundo2_noi18n("update-overlay-selection-mask")), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { KisSelectionMaskSP oldMask = m_image->m_d->overlaySelectionMask; KisSelectionMaskSP newMask = m_image->m_d->targetOverlaySelectionMask; if (oldMask == newMask) return; KIS_SAFE_ASSERT_RECOVER_RETURN(!newMask || newMask->graphListener() == m_image); m_image->m_d->overlaySelectionMask = newMask; if (oldMask || newMask) { m_image->m_d->rootLayer->notifyChildMaskChanged(); } if (oldMask) { m_image->m_d->rootLayer->setDirtyDontResetAnimationCache(oldMask->extent()); } if (newMask) { newMask->setDirty(); } m_image->undoAdapter()->emitSelectionChanged(); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new UpdateOverlaySelectionStroke(this)); endStroke(id); } KisSelectionMaskSP KisImage::overlaySelectionMask() const { return m_d->overlaySelectionMask; } bool KisImage::hasOverlaySelectionMask() const { return m_d->overlaySelectionMask; } KisSelectionSP KisImage::globalSelection() const { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (selectionMask) { return selectionMask->selection(); } else { return 0; } } void KisImage::setGlobalSelection(KisSelectionSP globalSelection) { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (!globalSelection) { if (selectionMask) { removeNode(selectionMask); } } else { if (!selectionMask) { selectionMask = new KisSelectionMask(this); selectionMask->initSelection(m_d->rootLayer); addNode(selectionMask); // If we do not set the selection now, the setActive call coming next // can be very, very expensive, depending on the size of the image. selectionMask->setSelection(globalSelection); selectionMask->setActive(true); } else { selectionMask->setSelection(globalSelection); } KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->childCount() > 0); KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->selectionMask()); } m_d->deselectedGlobalSelection = 0; m_d->legacyUndoAdapter.emitSelectionChanged(); } void KisImage::deselectGlobalSelection() { KisSelectionSP savedSelection = globalSelection(); setGlobalSelection(0); m_d->deselectedGlobalSelection = savedSelection; } bool KisImage::canReselectGlobalSelection() { return m_d->deselectedGlobalSelection; } void KisImage::reselectGlobalSelection() { if(m_d->deselectedGlobalSelection) { setGlobalSelection(m_d->deselectedGlobalSelection); } } QString KisImage::nextLayerName(const QString &_baseName) const { QString baseName = _baseName; if (m_d->nserver.currentSeed() == 0) { m_d->nserver.number(); return i18n("background"); } if (baseName.isEmpty()) { baseName = i18n("Layer"); } return QString("%1 %2").arg(baseName).arg(m_d->nserver.number()); } void KisImage::rollBackLayerName() { m_d->nserver.rollback(); } KisCompositeProgressProxy* KisImage::compositeProgressProxy() { return &m_d->compositeProgressProxy; } bool KisImage::locked() const { return m_d->lockCount != 0; } void KisImage::barrierLock(bool readOnly) { if (!locked()) { requestStrokeEnd(); m_d->scheduler.barrierLock(); m_d->lockedForReadOnly = readOnly; } else { m_d->lockedForReadOnly &= readOnly; } m_d->lockCount++; } bool KisImage::tryBarrierLock(bool readOnly) { bool result = true; if (!locked()) { result = m_d->scheduler.tryBarrierLock(); m_d->lockedForReadOnly = readOnly; } if (result) { m_d->lockCount++; m_d->lockedForReadOnly &= readOnly; } return result; } bool KisImage::isIdle(bool allowLocked) { return (allowLocked || !locked()) && m_d->scheduler.isIdle(); } void KisImage::lock() { if (!locked()) { requestStrokeEnd(); m_d->scheduler.lock(); } m_d->lockCount++; m_d->lockedForReadOnly = false; } void KisImage::unlock() { Q_ASSERT(locked()); if (locked()) { m_d->lockCount--; if (m_d->lockCount == 0) { m_d->scheduler.unlock(!m_d->lockedForReadOnly); } } } void KisImage::blockUpdates() { m_d->scheduler.blockUpdates(); } void KisImage::unblockUpdates() { m_d->scheduler.unblockUpdates(); } void KisImage::setSize(const QSize& size) { m_d->width = size.width(); m_d->height = size.height(); } void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if (newRect == bounds() && !cropLayers) return; KUndo2MagicString actionName = cropLayers ? kundo2_i18n("Crop Image") : kundo2_i18n("Resize Image"); KisImageSignalVector emitSignals; emitSignals << ComplexSizeChangedSignal(newRect, newRect.size()); emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(cropLayers ? KisCropSavedExtraData::CROP_IMAGE : KisCropSavedExtraData::RESIZE_IMAGE, newRect); KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName, extraData); if (cropLayers || !newRect.topLeft().isNull()) { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.applyCommand(new KisImageResizeCommand(this, newRect.size())); applicator.end(); } void KisImage::resizeImage(const QRect& newRect) { resizeImageImpl(newRect, false); } void KisImage::cropImage(const QRect& newRect) { resizeImageImpl(newRect, true); } void KisImage::cropNode(KisNodeSP node, const QRect& newRect) { bool isLayer = qobject_cast(node.data()); KUndo2MagicString actionName = isLayer ? kundo2_i18n("Crop Layer") : kundo2_i18n("Crop Mask"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER, newRect, node); KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName, extraData); KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, true, false); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy) { bool resolutionChanged = xres != xRes() && yres != yRes(); bool sizeChanged = size != this->size(); if (!resolutionChanged && !sizeChanged) return; KisImageSignalVector emitSignals; if (resolutionChanged) emitSignals << ResolutionChangedSignal; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size); emitSignals << ModifiedSignal; KUndo2MagicString actionName = sizeChanged ? kundo2_i18n("Scale Image") : kundo2_i18n("Change Image Resolution"); KisProcessingApplicator::ProcessingFlags signalFlags = (resolutionChanged || sizeChanged) ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); qreal sx = qreal(size.width()) / this->size().width(); qreal sy = qreal(size.height()) / this->size().height(); QTransform shapesCorrection; if (resolutionChanged) { shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres); } KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(sx, sy, 0, 0, QPointF(), 0, 0, 0, filterStrategy, shapesCorrection); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resolutionChanged) { KUndo2Command *parent = new KisResetShapesCommand(m_d->rootLayer); new KisImageSetResolutionCommand(this, xres, yres, parent); applicator.applyCommand(parent); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, size)); } applicator.end(); } -void KisImage::scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy) +void KisImage::scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; + QPointF offset; + { + KisTransformWorker worker(0, + scaleX, scaleY, + 0, 0, 0, 0, + 0.0, + 0, 0, 0, 0); + QTransform transform = worker.transform(); + + offset = center - transform.map(center); + } + KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); - KisProcessingVisitorSP visitor = + KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(scaleX, scaleY, 0, 0, QPointF(), 0, - 0, 0, + offset.x(), offset.y(), filterStrategy); - applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); + visitor->setSelection(selection); + + if (selection) { + applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); + } else { + applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); + } + applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, + double radians, bool resizeImage, - double radians) + KisSelectionSP selection) { + // we can either transform (and resize) the whole image or + // transform a selection, we cannot do both at the same time + KIS_SAFE_ASSERT_RECOVER(!(bool(selection) && resizeImage)) { + selection = 0; + } + + const QRect baseBounds = + resizeImage ? bounds() : + selection ? selection->selectedExactRect() : + rootNode->exactBounds(); + QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, 0, 0, 0, 0, radians, 0, 0, 0, 0); QTransform transform = worker.transform(); if (resizeImage) { - QRect newRect = transform.mapRect(bounds()); + QRect newRect = transform.mapRect(baseBounds); newSize = newRect.size(); offset = -newRect.topLeft(); } else { - QPointF origin = QRectF(rootNode->exactBounds()).center(); + QPointF origin = QRectF(baseBounds).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && - (newSize.width() != width() || newSize.height() != height()); + (newSize.width() != baseBounds.width() || + newSize.height() != baseBounds.height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; - if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); + if (sizeChanged) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize); emitSignals << ModifiedSignal; // These flags determine whether updates are transferred to the UI during processing KisProcessingApplicator::ProcessingFlags signalFlags = sizeChanged ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, rootNode, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic"); - KisProcessingVisitorSP visitor = + KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); + if (selection) { + visitor->setSelection(selection); + } - applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); + if (selection) { + applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); + } else { + applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); + } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::rotateImage(double radians) { - rotateImpl(kundo2_i18n("Rotate Image"), root(), true, radians); + rotateImpl(kundo2_i18n("Rotate Image"), root(), radians, true, 0); } -void KisImage::rotateNode(KisNodeSP node, double radians) +void KisImage::rotateNode(KisNodeSP node, double radians, KisSelectionSP selection) { if (node->inherits("KisMask")) { - rotateImpl(kundo2_i18n("Rotate Mask"), node, false, radians); + rotateImpl(kundo2_i18n("Rotate Mask"), node, radians, false, selection); } else { - rotateImpl(kundo2_i18n("Rotate Layer"), node, false, radians); + rotateImpl(kundo2_i18n("Rotate Layer"), node, radians, false, selection); } } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, - const QPointF &origin) + KisSelectionSP selection) { + const QRect baseBounds = + resizeImage ? bounds() : + selection ? selection->selectedExactRect() : + rootNode->exactBounds(); + + const QPointF origin = QRectF(baseBounds).center(); + //angleX, angleY are in degrees const qreal pi = 3.1415926535897932385; const qreal deg2rad = pi / 180.0; qreal tanX = tan(angleX * deg2rad); qreal tanY = tan(angleY * deg2rad); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, tanX, tanY, origin.x(), origin.y(), 0, 0, 0, 0, 0); - QRect newRect = worker.transform().mapRect(bounds()); + QRect newRect = worker.transform().mapRect(baseBounds); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } - if (newSize == size()) return; + if (newSize == baseBounds.size()) return; KisImageSignalVector emitSignals; - if (resizeImage) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); + if (resizeImage) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize); emitSignals << ModifiedSignal; KisProcessingApplicator::ProcessingFlags signalFlags = KisProcessingApplicator::RECURSIVE; if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES; KisProcessingApplicator applicator(this, rootNode, signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear"); - KisProcessingVisitorSP visitor = + KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); - applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); + if (selection) { + visitor->setSelection(selection); + } + + if (selection) { + applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); + } else { + applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); + } if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } -void KisImage::shearNode(KisNodeSP node, double angleX, double angleY) +void KisImage::shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection) { - QPointF shearOrigin = QRectF(bounds()).center(); - if (node->inherits("KisMask")) { shearImpl(kundo2_i18n("Shear Mask"), node, false, - angleX, angleY, shearOrigin); + angleX, angleY, selection); } else { shearImpl(kundo2_i18n("Shear Layer"), node, false, - angleX, angleY, shearOrigin); + angleX, angleY, selection); } } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, - angleX, angleY, QPointF()); + angleX, angleY, 0); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (!dstColorSpace) return; const KoColorSpace *srcColorSpace = m_d->colorSpace; undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); m_d->rootLayer->accept(visitor); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } bool KisImage::assignImageProfile(const KoColorProfile *profile) { if (!profile) return false; const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); const KoColorSpace *srcCs = colorSpace(); if (!dstCs) return false; m_d->colorSpace = dstCs; KisChangeProfileVisitor visitor(srcCs, dstCs); bool retval = m_d->rootLayer->accept(visitor); m_d->signalRouter.emitNotification(ProfileChangedSignal); return retval; } void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace) { if (*m_d->colorSpace == *dstColorSpace) return; undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace) { m_d->colorSpace = colorSpace; m_d->rootLayer->resetCache(); m_d->signalRouter.emitNotification(ColorSpaceChangedSignal); } const KoColorSpace * KisImage::colorSpace() const { return m_d->colorSpace; } const KoColorProfile * KisImage::profile() const { return colorSpace()->profile(); } double KisImage::xRes() const { return m_d->xres; } double KisImage::yRes() const { return m_d->yres; } void KisImage::setResolution(double xres, double yres) { m_d->xres = xres; m_d->yres = yres; m_d->signalRouter.emitNotification(ResolutionChangedSignal); } QPointF KisImage::documentToPixel(const QPointF &documentCoord) const { return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes()); } QPoint KisImage::documentToImagePixelFloored(const QPointF &documentCoord) const { QPointF pixelCoord = documentToPixel(documentCoord); return QPoint(qFloor(pixelCoord.x()), qFloor(pixelCoord.y())); } QRectF KisImage::documentToPixel(const QRectF &documentRect) const { return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight())); } QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const { return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes()); } QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const { return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes()); } QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const { return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight())); } qint32 KisImage::width() const { return m_d->width; } qint32 KisImage::height() const { return m_d->height; } KisGroupLayerSP KisImage::rootLayer() const { Q_ASSERT(m_d->rootLayer); return m_d->rootLayer; } KisPaintDeviceSP KisImage::projection() const { if (m_d->isolatedRootNode) { return m_d->isolatedRootNode->projection(); } Q_ASSERT(m_d->rootLayer); KisPaintDeviceSP projection = m_d->rootLayer->projection(); Q_ASSERT(projection); return projection; } qint32 KisImage::nlayers() const { QStringList list; list << "KisLayer"; KisCountVisitor visitor(list, KoProperties()); m_d->rootLayer->accept(visitor); return visitor.count(); } qint32 KisImage::nHiddenLayers() const { QStringList list; list << "KisLayer"; KoProperties properties; properties.setProperty("visible", false); KisCountVisitor visitor(list, properties); m_d->rootLayer->accept(visitor); return visitor.count(); } void KisImage::flatten() { 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; } void KisImage::setDefaultProjectionColor(const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer); m_d->rootLayer->setDefaultProjectionColor(color); } KoColor KisImage::defaultProjectionColor() const { KIS_ASSERT_RECOVER(m_d->rootLayer) { return KoColor(Qt::transparent, m_d->colorSpace); } return m_d->rootLayer->defaultProjectionColor(); } void KisImage::setRootLayer(KisGroupLayerSP rootLayer) { emit sigInternalStopIsolatedModeRequested(); KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace); if (m_d->rootLayer) { m_d->rootLayer->setGraphListener(0); m_d->rootLayer->disconnect(); KisPaintDeviceSP original = m_d->rootLayer->original(); defaultProjectionColor = original->defaultPixel(); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); m_d->rootLayer->setImage(this); 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(true); int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < rc.height(); y += patchHeight) { for (int x = 0; x < rc.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); patchRect &= rc; QtConcurrent::run(std::bind(&KisImage::notifyProjectionUpdated, q, patchRect)); } } } bool KisImage::startIsolatedMode(KisNodeSP node) { struct StartIsolatedModeStroke : public KisSimpleStrokeStrategy { StartIsolatedModeStroke(KisNodeSP node, KisImageSP image) : KisSimpleStrokeStrategy("start-isolated-mode", kundo2_noi18n("start-isolated-mode")), m_node(node), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { // pass-though node don't have any projection prepared, so we should // explicitly regenerate it before activating isolated mode. m_node->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); m_image->m_d->isolatedRootNode = m_node; emit m_image->sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds()); m_image->invalidateAllFrames(); } private: KisNodeSP m_node; KisImageSP m_image; }; KisStrokeId id = startStroke(new StartIsolatedModeStroke(node, this)); endStroke(id); return true; } void KisImage::stopIsolatedMode() { if (!m_d->isolatedRootNode) return; struct StopIsolatedModeStroke : public KisSimpleStrokeStrategy { StopIsolatedModeStroke(KisImageSP image) : KisSimpleStrokeStrategy("stop-isolated-mode", kundo2_noi18n("stop-isolated-mode")), m_image(image) { this->enableJob(JOB_INIT); setClearsRedoOnStart(false); } void initStrokeCallback() { if (!m_image->m_d->isolatedRootNode) return; //KisNodeSP oldRootNode = m_image->m_d->isolatedRootNode; m_image->m_d->isolatedRootNode = 0; emit m_image->sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds()); m_image->invalidateAllFrames(); // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); // oldRootNode->setDirty(updateRect); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new StopIsolatedModeStroke(this)); endStroke(id); } KisNodeSP KisImage::isolatedModeRoot() const { return m_d->isolatedRootNode; } void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data) { KisUpdateTimeMonitor::instance()->reportJobStarted(data); m_d->scheduler.addJob(id, data); } void KisImage::endStroke(KisStrokeId id) { m_d->scheduler.endStroke(id); } bool KisImage::cancelStroke(KisStrokeId id) { return m_d->scheduler.cancelStroke(id); } bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync() { return scheduler.tryCancelCurrentStrokeAsync(); } void KisImage::requestUndoDuringStroke() { emit sigUndoDuringStrokeRequested(); } void KisImage::requestStrokeCancellation() { if (!m_d->tryCancelCurrentStrokeAsync()) { emit sigStrokeCancellationRequested(); } } UndoResult KisImage::tryUndoUnfinishedLod0Stroke() { return m_d->scheduler.tryUndoLastStrokeAsync(); } void KisImage::requestStrokeEnd() { emit sigStrokeEndRequested(); emit sigStrokeEndRequestedActiveNodeFiltered(); } void KisImage::requestStrokeEndActiveNode() { emit sigStrokeEndRequested(); } void KisImage::refreshGraph(KisNodeSP root) { refreshGraph(root, bounds(), bounds()); } void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefresh(root, rc, cropRect); } void KisImage::initialRefreshGraph() { /** * NOTE: Tricky part. We set crop rect to null, so the clones * will not rely on precalculated projections of their sources */ refreshGraphAsync(0, bounds(), QRect()); waitForDone(); } void KisImage::refreshGraphAsync(KisNodeSP root) { refreshGraphAsync(root, bounds(), bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc) { refreshGraphAsync(root, rc, bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefreshAsync(root, rc, cropRect); } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect) { KIS_ASSERT_RECOVER_RETURN(pseudoFilthy); m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false); m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect); } void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { m_d->scheduler.addSpontaneousJob(spontaneousJob); } void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) { // update filters are *not* recursive! KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter); m_d->projectionUpdatesFilter = filter; } KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const { return m_d->projectionUpdatesFilter; } void KisImage::disableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter())); } void KisImage::enableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP()); } void KisImage::disableUIUpdates() { m_d->disableUIUpdateSignals.ref(); } void KisImage::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::setWorkingThreadsLimit(int value) { m_d->scheduler.setThreadsLimit(value); } int KisImage::workingThreadsLimit() const { return m_d->scheduler.threadsLimit(); } void KisImage::notifySelectionChanged() { /** * The selection is calculated asynchromously, so it is not * handled by disableUIUpdates() and other special signals of * KisImageSignalRouter */ m_d->legacyUndoAdapter.emitSelectionChanged(); /** * Editing of selection masks doesn't necessary produce a * setDirty() call, so in the end of the stroke we need to request * direct update of the UI's cache. */ if (m_d->isolatedRootNode && dynamic_cast(m_d->isolatedRootNode.data())) { notifyProjectionUpdated(bounds()); } } void KisImage::requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect) { if (rects.isEmpty()) return; m_d->scheduler.updateProjection(node, rects, cropRect); } void KisImage::requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) { if (m_d->projectionUpdatesFilter && m_d->projectionUpdatesFilter->filter(this, node, rects, resetAnimationCache)) { return; } if (resetAnimationCache) { m_d->animationInterface->notifyNodeChanged(node, rects, false); } /** * Here we use 'permitted' instead of 'active' intentively, * because the updates may come after the actual stroke has been * finished. And having some more updates for the stroke not * supporting the wrap-around mode will not make much harm. */ if (m_d->wrapAroundModePermitted) { QVector allSplitRects; const QRect boundRect = effectiveLodBounds(); Q_FOREACH (const QRect &rc, rects) { KisWrappedRect splitRect(rc, boundRect); allSplitRects.append(splitRect); } requestProjectionUpdateImpl(node, allSplitRects, boundRect); } else { requestProjectionUpdateImpl(node, rects, bounds()); } KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache); } void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect) { m_d->animationInterface->invalidateFrames(range, rect); } void KisImage::requestTimeSwitch(int time) { m_d->animationInterface->requestTimeSwitchNonGUI(time); } KisNode *KisImage::graphOverlayNode() const { return m_d->overlaySelectionMask.data(); } QList KisImage::compositions() { return m_d->compositions; } void KisImage::addComposition(KisLayerCompositionSP composition) { m_d->compositions.append(composition); } void KisImage::removeComposition(KisLayerCompositionSP composition) { m_d->compositions.removeAll(composition); } bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds) { KisSelectionMask *mask = dynamic_cast(root.data()); if (mask && (!bounds.contains(mask->paintDevice()->exactBounds()) || mask->selection()->hasShapeSelection())) { return true; } KisNodeSP node = root->firstChild(); while (node) { if (checkMasksNeedConversion(node, bounds)) { return true; } node = node->nextSibling(); } return false; } void KisImage::setWrapAroundModePermitted(bool value) { if (m_d->wrapAroundModePermitted != value) { requestStrokeEnd(); } m_d->wrapAroundModePermitted = value; if (m_d->wrapAroundModePermitted && checkMasksNeedConversion(root(), bounds())) { KisProcessingApplicator applicator(this, root(), KisProcessingApplicator::RECURSIVE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Crop Selections")); KisProcessingVisitorSP visitor = new KisCropSelectionsProcessingVisitor(bounds()); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } } bool KisImage::wrapAroundModePermitted() const { return m_d->wrapAroundModePermitted; } bool KisImage::wrapAroundModeActive() const { return m_d->wrapAroundModePermitted && m_d->scheduler.wrapAroundModeSupported(); } void KisImage::setDesiredLevelOfDetail(int lod) { if (m_d->blockLevelOfDetail) { qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()" << "was called while LoD functionality was being blocked!"; return; } m_d->scheduler.setDesiredLevelOfDetail(lod); } int KisImage::currentLevelOfDetail() const { if (m_d->blockLevelOfDetail) { return 0; } return m_d->scheduler.currentLevelOfDetail(); } void KisImage::setLevelOfDetailBlocked(bool value) { KisImageBarrierLockerRaw l(this); if (value && !m_d->blockLevelOfDetail) { m_d->scheduler.setDesiredLevelOfDetail(0); } m_d->blockLevelOfDetail = value; } void KisImage::explicitRegenerateLevelOfDetail() { if (!m_d->blockLevelOfDetail) { m_d->scheduler.explicitRegenerateLevelOfDetail(); } } bool KisImage::levelOfDetailBlocked() const { return m_d->blockLevelOfDetail; } void KisImage::notifyNodeCollpasedChanged() { emit sigNodeCollapsedChanged(); } KisImageAnimationInterface* KisImage::animationInterface() const { return m_d->animationInterface; } void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig) { m_d->proofingConfig = proofingConfig; emit sigProofingConfigChanged(); } KisProofingConfigurationSP KisImage::proofingConfiguration() const { if (m_d->proofingConfig) { return m_d->proofingConfig; } return KisProofingConfigurationSP(); } QPointF KisImage::mirrorAxesCenter() const { return m_d->axesCenter; } void KisImage::setMirrorAxesCenter(const QPointF &value) const { m_d->axesCenter = value; } diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h index ba68762313..148ebd2da8 100644 --- a/libs/image/kis_image.h +++ b/libs/image/kis_image.h @@ -1,1121 +1,1120 @@ /* * 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 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); ~KisImage() override; public: // KisNodeGraphListener implementation void aboutToAddANode(KisNode *parent, int index) override; void nodeHasBeenAdded(KisNode *parent, int index) override; void aboutToRemoveANode(KisNode *parent, int index) override; void nodeChanged(KisNode * node) override; void invalidateAllFrames() override; void notifySelectionChanged() override; void requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) override; void invalidateFrames(const KisTimeRange &range, const QRect &rect) override; void requestTimeSwitch(int time) override; KisNode* graphOverlayNode() const override; public: // KisProjectionUpdateListener implementation void notifyProjectionUpdated(const QRect &rc) override; public: /** * Set the number of threads used by the image's working threads */ void setWorkingThreadsLimit(int value); /** * Return the number of threads available to the image's working threads */ int workingThreadsLimit() const; /** * Makes a copy of the image with all the layers. If possible, shallow * copies of the layers are made. * * \p exactCopy shows if the copied image should look *exactly* the same as * the other one (according to it's .kra xml representation). It means that * the layers will have the same UUID keys and, therefore, you are not * expected to use the copied image anywhere except for saving. Don't use * this option if you plan to work with the copied image later. */ KisImage *clone(bool exactCopy = false); /** * Render the projection onto a QImage. */ QImage convertToQImage(qint32 x1, qint32 y1, qint32 width, qint32 height, const KoColorProfile * profile); /** * Render the projection onto a QImage. * (this is an overloaded function) */ QImage convertToQImage(QRect imageRect, const KoColorProfile * profile); /** * Render a thumbnail of the projection onto a QImage. */ QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile); /** * [low-level] Lock the image without waiting for all the internal job queues are processed * * WARNING: Don't use it unless you really know what you are doing! Use barrierLock() instead! * * Waits for all the **currently running** internal jobs to complete and locks the image * for writing. Please note that this function does **not** wait for all the internal * queues to process, so there might be some non-finished actions pending. It means that * you just postpone these actions until you unlock() the image back. Until then, then image * might easily be frozen in some inconsistent state. * * The only sane usage for this function is to lock the image for **emergency** * processing, when some internal action or scheduler got hung up, and you just want * to fetch some data from the image without races. * * In all other cases, please use barrierLock() instead! */ void lock(); /** * Unlocks the image and starts/resumes all the pending internal jobs. If the image * has been locked for a non-readOnly access, then all the internal caches of the image * (e.g. lod-planes) are reset and regeneration jobs are scheduled. */ void unlock(); /** * @return return true if the image is in a locked state, i.e. all the internal * jobs are blocked from execution by calling wither lock() or barrierLock(). * * When the image is locked, the user can do some modifications to the image * contents safely without a perspective having race conditions with internal * image jobs. */ bool locked() const; /** * Sets the mask (it must be a part of the node hierarchy already) to be paited on * the top of all layers. This method does all the locking and syncing for you. It * is executed asynchronously. */ void setOverlaySelectionMask(KisSelectionMaskSP mask); /** * \see setOverlaySelectionMask */ KisSelectionMaskSP overlaySelectionMask() const; /** * \see setOverlaySelectionMask */ bool hasOverlaySelectionMask() const; /** * @return the global selection object or 0 if there is none. The * global selection is always read-write. */ KisSelectionSP globalSelection() const; /** * Retrieve the next automatic layername (XXX: fix to add option to return Mask X) */ QString nextLayerName(const QString &baseName = "") const; /** * Set the automatic layer name counter one back. */ void rollBackLayerName(); /** * @brief start asynchronous operation on resizing the image * * The method will resize the image to fit the new size without * dropping any pixel data. The GUI will get correct * notification with old and new sizes, so it adjust canvas origin * accordingly and avoid jumping of the canvas on screen * * @param newRect the rectangle of the image which will be visible * after operation is completed * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after ths call. */ void resizeImage(const QRect& newRect); /** * @brief start asynchronous operation on cropping the image * * The method will **drop** all the image data outside \p newRect * and resize the image to fit the new size. The GUI will get correct * notification with old and new sizes, so it adjust canvas origin * accordingly and avoid jumping of the canvas on screen * * @param newRect the rectangle of the image which will be cut-out * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after ths call. */ void cropImage(const QRect& newRect); /** * @brief start asynchronous operation on cropping a subtree of nodes starting at \p node * * The method will **drop** all the layer data outside \p newRect. Neither * image nor a layer will be moved anywhere * * @param newRect the rectangle of the layer which will be cut-out * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after ths call. */ void cropNode(KisNodeSP node, const QRect& newRect); /** * @brief start asynchronous operation on scaling the image * @param size new image size in pixels * @param xres new image x-resolution pixels-per-pt * @param yres new image y-resolution pixels-per-pt * @param filterStrategy filtering strategy * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after ths call. */ void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy); /** * @brief start asynchronous operation on scaling a subtree of nodes starting at \p node * @param node node to scale * @param scaleX, @param scaleY scale coefficient to be applied to the node * @param filterStrategy filtering strategy * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after ths call. */ - void scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy); + void scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection); /** * @brief start asynchronous operation on rotating the image * * The image is resized to fit the rotated rectangle * * @param radians rotation angle in radians * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void rotateImage(double radians); /** * @brief start asynchronous operation on rotating a subtree of nodes starting at \p node * * The image is not resized! * * @param node the root of the subtree to rotate * @param radians rotation angle in radians * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ - void rotateNode(KisNodeSP node, double radians); + void rotateNode(KisNodeSP node, double radians, KisSelectionSP selection); /** * @brief start asynchronous operation on shearing the image * * The image is resized to fit the sheared polygon * * @param angleX, @param angleY are given in degrees. * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void shear(double angleX, double angleY); /** * @brief start asynchronous operation on shearing a subtree of nodes starting at \p node * * The image is not resized! * * @param node the root of the subtree to rotate * @param angleX, @param angleY are given in degrees. * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ - void shearNode(KisNodeSP node, double angleX, double angleY); + void shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection); /** * Convert the image and all its layers to the dstColorSpace */ void convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Set the color space of the projection (and the root layer) * to dstColorSpace. No conversion is done for other layers, * their colorspace can differ. * NOTE: 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 * and executes commands exclusively. */ KisUndoAdapter* undoAdapter() const; /** * This adapter is used by the strokes system. The commands are added * to it *after* redo() is done (in the scheduler context). They are * wrapped into a special command and added to the undo stack. redo() * in not called. */ KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const override; /** * Replace current undo store with the new one. The old store * will be deleted. * This method is used by KisDocument for dropping all the commands * during file loading. */ void setUndoStore(KisUndoStore *undoStore); /** * Return current undo store of the image */ KisUndoStore* undoStore(); /** * Tell the image it's modified; this emits the sigImageModified * signal. This happens when the image needs to be saved */ void setModified(); /** * The default colorspace of this image: new layers will have this * colorspace and the projection will have this colorspace. */ const KoColorSpace * colorSpace() const; /** * X resolution in pixels per pt */ double xRes() const; /** * Y resolution in pixels per pt */ double yRes() const; /** * Set the resolution in pixels per pt. */ void setResolution(double xres, double yres); /** * Convert a document coordinate to a pixel coordinate. * * @param documentCoord PostScript Pt coordinate to convert. */ QPointF documentToPixel(const QPointF &documentCoord) const; /** * Convert a document coordinate to an integer pixel coordinate rounded down. * * @param documentCoord PostScript Pt coordinate to convert. */ QPoint documentToImagePixelFloored(const QPointF &documentCoord) const; /** * Convert a document rectangle to a pixel rectangle. * * @param documentRect PostScript Pt rectangle to convert. */ QRectF documentToPixel(const QRectF &documentRect) const; /** * Convert a pixel coordinate to a document coordinate. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPointF &pixelCoord) const; /** * Convert an integer pixel coordinate to a document coordinate. * The document coordinate is at the centre of the pixel. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPoint &pixelCoord) const; /** * Convert a document rectangle to an integer pixel rectangle. * * @param pixelCoord pixel coordinate to convert. */ QRectF pixelToDocument(const QRectF &pixelCoord) const; /** * Return the width of the image */ qint32 width() const; /** * Return the height of the image */ qint32 height() const; /** * Return the size of the image */ QSize size() const { return QSize(width(), height()); } /** * @return the root node of the image node graph */ KisGroupLayerSP rootLayer() const; /** * Return the projection; that is, the complete, composited * representation of this image. */ KisPaintDeviceSP projection() const; /** * Return the number of layers (not other nodes) that are in this * image. */ qint32 nlayers() const; /** * Return the number of layers (not other node types) that are in * this image and that are hidden. */ qint32 nHiddenLayers() const; /** * Merge all visible layers and discard hidden ones. */ void flatten(); /** * 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 current level of detail which is used when processing the image. * Current working zoom = 2 ^ (- currentLevelOfDetail()). Default value is * null, which means we work on the original image. */ int currentLevelOfDetail() const; /** * Notify KisImage which level of detail should be used in the * lod-mode. Setting the mode does not guarantee the LOD to be * used. It will be activated only when the stokes supports it. */ void setDesiredLevelOfDetail(int lod); /** * Relative position of the mirror axis center * 0,0 - topleft corner of the image * 1,1 - bottomright corner of the image */ QPointF mirrorAxesCenter() const; /** * Sets the relative position of the axes center * \see mirrorAxesCenter() for details */ void setMirrorAxesCenter(const QPointF &value) const; public Q_SLOTS: /** * Explicitly start regeneration of LoD planes of all the devices * in the image. This call should be performed when the user is idle, * just to make the quality of image updates better. */ void explicitRegenerateLevelOfDetail(); public: /** * Blocks usage of level of detail functionality. After this method * has been called, no new strokes will use LoD. */ void setLevelOfDetailBlocked(bool value); /** * \see setLevelOfDetailBlocked() */ bool levelOfDetailBlocked() const; /** * Notifies that the node collapsed state has changed */ void notifyNodeCollpasedChanged(); KisImageAnimationInterface *animationInterface() const; /** * @brief setProofingConfiguration, this sets the image's proofing configuration, and signals * the proofingConfiguration has changed. * @param proofingConfig - the kis proofing config that will be used instead. */ void setProofingConfiguration(KisProofingConfigurationSP proofingConfig); /** * @brief proofingConfiguration * @return the proofing configuration of the image. */ KisProofingConfigurationSP proofingConfiguration() const; public Q_SLOTS: bool startIsolatedMode(KisNodeSP node); void stopIsolatedMode(); public: KisNodeSP isolatedModeRoot() const; Q_SIGNALS: /** * Emitted whenever an action has caused the image to be * recomposited. * * @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(); /** * Internal signal for asynchronously requesting isolated mode to stop. Don't use it * outside KisImage, use sigIsolatedModeChanged() instead. */ void sigInternalStopIsolatedModeRequested(); public Q_SLOTS: KisCompositeProgressProxy* compositeProgressProxy(); bool isIdle(bool allowLocked = false); /** * @brief Wait until all the queued background jobs are completed and lock the image. * * KisImage object has a local scheduler that executes long-running image * rendering/modifying jobs (we call them "strokes") in a background. Basically, * one should either access the image from the scope of such jobs (strokes) or * just lock the image before using. * * Calling barrierLock() will wait until all the queued operations are finished * and lock the image, so you can start accessing it in a safe way. * * @p readOnly tells the image if the caller is going to modify the image during * holding the lock. Locking with non-readOnly access will reset all * the internal caches of the image (lod-planes) when the lock status * will be lifted. */ void barrierLock(bool readOnly = false); /** * @brief Tries to lock the image without waiting for the jobs to finish * * Same as barrierLock(), but doesn't block execution of the calling thread * until all the background jobs are finished. Instead, in case of presence of * unfinished jobs in the queue, it just returns false * * @return whether the lock has been acquired * @see barrierLock */ bool tryBarrierLock(bool readOnly = false); /** * Wait for all the internal image jobs to complete and return without locking * the image. This function is handly for tests or other synchronous actions, * when one needs to wait for the result of his actions. */ void waitForDone(); KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override; void addJob(KisStrokeId id, KisStrokeJobData *data) override; void endStroke(KisStrokeId id) override; bool cancelStroke(KisStrokeId id) override; /** * @brief blockUpdates block updating the image projection */ void blockUpdates() override; /** * @brief unblockUpdates unblock updating the image project. This * only restarts the scheduler and does not schedule a full refresh. */ void unblockUpdates() override; /** * Disables notification of the UI about the changes in the image. * This feature is used by KisProcessingApplicator. It is needed * when we change the size of the image. In this case, the whole * image will be reloaded into UI by sigSizeChanged(), so there is * no need to inform the UI about individual dirty rects. */ void disableUIUpdates() override; /** * \see disableUIUpdates */ void enableUIUpdates() override; /** * Disables the processing of all the setDirty() requests that * come to the image. The incoming requests are effectively * *dropped*. * * This feature is used by KisProcessingApplicator. For many cases * it provides its own updates interface, which recalculates the * whole subtree of nodes. But while we change any particular * node, it can ask for an update itself. This method is a way of * blocking such intermediate (and excessive) requests. * * NOTE: this is a convenience function for setProjectionUpdatesFilter() * that installs a predefined filter that eats everything. Please * note that these calls are *not* recursive */ void disableDirtyRequests() override; /** * \see disableDirtyRequests() */ void enableDirtyRequests() override; /** * Installs a filter object that will filter all the incoming projection update * requests. If the filter return true, the incoming update is dropped. * * NOTE: you cannot set filters recursively! */ void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) override; /** * \see setProjectionUpdatesFilter() */ KisProjectionUpdatesFilterSP projectionUpdatesFilter() const override; void refreshGraphAsync(KisNodeSP root = KisNodeSP()) override; void refreshGraphAsync(KisNodeSP root, const QRect &rc) override; void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) override; /** * Triggers synchronous recomposition of the projection */ void refreshGraph(KisNodeSP root = KisNodeSP()); void refreshGraph(KisNodeSP root, const QRect& rc, const QRect &cropRect); void initialRefreshGraph(); /** * Initiate a stack regeneration skipping the recalculation of the * filthy node's projection. * * Works exactly as pseudoFilthy->setDirty() with the only * exception that pseudoFilthy::updateProjection() will not be * called. That is used by KisRecalculateTransformMaskJob to avoid * cyclic dependencies. */ void requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect); /** * Adds a spontaneous job to the updates queue. * * A spontaneous job may do some trivial tasks in the background, * like updating the outline of selection or purging unused tiles * from the existing paint devices. */ void addSpontaneousJob(KisSpontaneousJob *spontaneousJob); /** * 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 rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians, + bool resizeImage, KisSelectionSP selection); void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, - bool resizeImage, double angleX, double angleY, - const QPointF &origin); + bool resizeImage, double angleX, double angleY, KisSelectionSP selection); void safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2); void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea); void requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect); friend class KisImageResizeCommand; void setSize(const QSize& size); friend class KisImageSetProjectionColorSpaceCommand; void setProjectionColorSpace(const KoColorSpace * colorSpace); friend class KisDeselectGlobalSelectionCommand; friend class KisReselectGlobalSelectionCommand; friend class KisSetGlobalSelectionCommand; friend class KisImageTest; friend class Document; // For libkis /** * Replaces the current global selection with globalSelection. If * \p globalSelection is empty, removes the selection object, so that * \ref globalSelection() will return 0 after that. */ void setGlobalSelection(KisSelectionSP globalSelection); /** * Deselects current global selection. * \ref globalSelection() will return 0 after that. */ void deselectGlobalSelection(); /** * Reselects current deselected selection * * \see deselectGlobalSelection() */ void reselectGlobalSelection(); private: class KisImagePrivate; KisImagePrivate * m_d; }; #endif // KIS_IMAGE_H_ diff --git a/libs/image/kis_processing_applicator.cpp b/libs/image/kis_processing_applicator.cpp index f8d101b198..08f493c3ed 100644 --- a/libs/image/kis_processing_applicator.cpp +++ b/libs/image/kis_processing_applicator.cpp @@ -1,333 +1,345 @@ /* * 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_processing_applicator.h" #include "kis_image.h" #include "kis_node.h" #include "kis_clone_layer.h" #include "kis_processing_visitor.h" #include "commands_new/kis_processing_command.h" #include "kis_stroke_strategy_undo_command_based.h" #include "kis_layer_utils.h" #include "kis_command_utils.h" class DisableUIUpdatesCommand : public KisCommandUtils::FlipFlopCommand { public: DisableUIUpdatesCommand(KisImageWSP image, bool finalUpdate) : FlipFlopCommand(finalUpdate), m_image(image) { } void init() override { m_image->disableUIUpdates(); } void end() override { m_image->enableUIUpdates(); } private: KisImageWSP m_image; }; class UpdateCommand : public KisCommandUtils::FlipFlopCommand { public: UpdateCommand(KisImageWSP image, KisNodeSP node, KisProcessingApplicator::ProcessingFlags flags, bool finalUpdate) : FlipFlopCommand(finalUpdate), m_image(image), m_node(node), m_flags(flags) { } private: void init() override { /** * We disable all non-centralized updates here. Everything * should be done by this command's explicit updates. * * If you still need third-party updates work, please add a * flag to the applicator. */ m_image->disableDirtyRequests(); } void end() override { m_image->enableDirtyRequests(); if(m_flags.testFlag(KisProcessingApplicator::RECURSIVE)) { m_image->refreshGraphAsync(m_node); } m_node->setDirty(m_image->bounds()); updateClones(m_node); } void updateClones(KisNodeSP node) { // simple tail-recursive iteration KisNodeSP prevNode = node->lastChild(); while(prevNode) { updateClones(prevNode); prevNode = prevNode->prevSibling(); } KisLayer *layer = qobject_cast(m_node.data()); if(layer && layer->hasClones()) { Q_FOREACH (KisCloneLayerSP clone, layer->registeredClones()) { if(!clone) continue; QPoint offset(clone->x(), clone->y()); QRegion dirtyRegion(m_image->bounds()); dirtyRegion -= m_image->bounds().translated(offset); clone->setDirty(dirtyRegion); } } } private: KisImageWSP m_image; KisNodeSP m_node; KisProcessingApplicator::ProcessingFlags m_flags; }; class EmitImageSignalsCommand : public KisCommandUtils::FlipFlopCommand { public: EmitImageSignalsCommand(KisImageWSP image, KisImageSignalVector emitSignals, bool finalUpdate) : FlipFlopCommand(finalUpdate), m_image(image), m_emitSignals(emitSignals) { } void end() override { if (isFinalizing()) { doUpdate(m_emitSignals); } else { KisImageSignalVector reverseSignals; KisImageSignalVector::iterator i = m_emitSignals.end(); while (i != m_emitSignals.begin()) { --i; reverseSignals.append(i->inverted()); } doUpdate(reverseSignals); } } private: void doUpdate(KisImageSignalVector emitSignals) { Q_FOREACH (KisImageSignalType type, emitSignals) { m_image->signalRouter()->emitNotification(type); } } private: KisImageWSP m_image; KisImageSignalVector m_emitSignals; }; KisProcessingApplicator::KisProcessingApplicator(KisImageWSP image, KisNodeSP node, ProcessingFlags flags, KisImageSignalVector emitSignals, const KUndo2MagicString &name, KUndo2CommandExtraData *extraData, int macroId) : m_image(image), m_node(node), m_flags(flags), m_emitSignals(emitSignals), m_finalSignalsEmitted(false) { KisStrokeStrategyUndoCommandBased *strategy = new KisStrokeStrategyUndoCommandBased(name, false, m_image.data()); if (m_flags.testFlag(SUPPORTS_WRAPAROUND_MODE)) { strategy->setSupportsWrapAroundMode(true); } if (extraData) { strategy->setCommandExtraData(extraData); } strategy->setMacroId(macroId); m_strokeId = m_image->startStroke(strategy); if(!m_emitSignals.isEmpty()) { applyCommand(new EmitImageSignalsCommand(m_image, m_emitSignals, false), KisStrokeJobData::BARRIER); } if(m_flags.testFlag(NO_UI_UPDATES)) { applyCommand(new DisableUIUpdatesCommand(m_image, false), KisStrokeJobData::BARRIER); } if (m_node) { applyCommand(new UpdateCommand(m_image, m_node, m_flags, false)); } } KisProcessingApplicator::~KisProcessingApplicator() { } void KisProcessingApplicator::applyVisitor(KisProcessingVisitorSP visitor, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { + KUndo2Command *initCommand = visitor->createInitCommand(); + if (initCommand) { + applyCommand(initCommand, + KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); + } + if(!m_flags.testFlag(RECURSIVE)) { applyCommand(new KisProcessingCommand(visitor, m_node), sequentiality, exclusivity); } else { visitRecursively(m_node, visitor, sequentiality, exclusivity); } } void KisProcessingApplicator::applyVisitorAllFrames(KisProcessingVisitorSP visitor, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { + KUndo2Command *initCommand = visitor->createInitCommand(); + if (initCommand) { + applyCommand(initCommand, + KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); + } + KisLayerUtils::FrameJobs jobs; // TODO: implement a nonrecursive case when !m_flags.testFlag(RECURSIVE) // (such case is not yet used anywhere) KIS_SAFE_ASSERT_RECOVER_NOOP(m_flags.testFlag(RECURSIVE)); KisLayerUtils::updateFrameJobsRecursive(&jobs, m_node); if (jobs.isEmpty()) { applyVisitor(visitor, sequentiality, exclusivity); return; } KisLayerUtils::FrameJobs::const_iterator it = jobs.constBegin(); KisLayerUtils::FrameJobs::const_iterator end = jobs.constEnd(); KisLayerUtils::SwitchFrameCommand::SharedStorageSP switchFrameStorage( new KisLayerUtils::SwitchFrameCommand::SharedStorage()); for (; it != end; ++it) { const int frame = it.key(); const QSet &nodes = it.value(); applyCommand(new KisLayerUtils::SwitchFrameCommand(m_image, frame, false, switchFrameStorage), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); foreach (KisNodeSP node, nodes) { applyCommand(new KisProcessingCommand(visitor, node), sequentiality, exclusivity); } applyCommand(new KisLayerUtils::SwitchFrameCommand(m_image, frame, true, switchFrameStorage), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); } } void KisProcessingApplicator::visitRecursively(KisNodeSP node, KisProcessingVisitorSP visitor, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { // simple tail-recursive iteration KisNodeSP prevNode = node->lastChild(); while(prevNode) { visitRecursively(prevNode, visitor, sequentiality, exclusivity); prevNode = prevNode->prevSibling(); } applyCommand(new KisProcessingCommand(visitor, node), sequentiality, exclusivity); } void KisProcessingApplicator::applyCommand(KUndo2Command *command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { /* * One should not add commands after the final signals have been * emitted, only end or cancel the stroke */ KIS_ASSERT_RECOVER_RETURN(!m_finalSignalsEmitted); m_image->addJob(m_strokeId, new KisStrokeStrategyUndoCommandBased::Data(KUndo2CommandSP(command), false, sequentiality, exclusivity)); } void KisProcessingApplicator::explicitlyEmitFinalSignals() { KIS_ASSERT_RECOVER_RETURN(!m_finalSignalsEmitted); if (m_node) { applyCommand(new UpdateCommand(m_image, m_node, m_flags, true)); } if(m_flags.testFlag(NO_UI_UPDATES)) { applyCommand(new DisableUIUpdatesCommand(m_image, true), KisStrokeJobData::BARRIER); } if(!m_emitSignals.isEmpty()) { applyCommand(new EmitImageSignalsCommand(m_image, m_emitSignals, true), KisStrokeJobData::BARRIER); } // simple consistency check m_finalSignalsEmitted = true; } void KisProcessingApplicator::end() { if (!m_finalSignalsEmitted) { explicitlyEmitFinalSignals(); } m_image->endStroke(m_strokeId); } void KisProcessingApplicator::cancel() { m_image->cancelStroke(m_strokeId); } void KisProcessingApplicator::runSingleCommandStroke(KisImageSP image, KUndo2Command *cmd, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, cmd->text()); applicator.applyCommand(cmd, sequentiality, exclusivity); applicator.end(); } diff --git a/libs/image/kis_processing_visitor.cpp b/libs/image/kis_processing_visitor.cpp index accd4ae993..7aa9938ab8 100644 --- a/libs/image/kis_processing_visitor.cpp +++ b/libs/image/kis_processing_visitor.cpp @@ -1,58 +1,63 @@ /* * 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_processing_visitor.h" #include #include #include "kis_node_progress_proxy.h" #include "kis_node.h" #include KisProcessingVisitor::ProgressHelper::ProgressHelper(const KisNode *node) { KisNodeProgressProxy *progressProxy = node->nodeProgressProxy(); if(progressProxy) { m_progressUpdater = new KoProgressUpdater(progressProxy); m_progressUpdater->setObjectName("ProgressHelper::m_progressUpdater"); m_progressUpdater->start(100, i18n("Processing")); m_progressUpdater->moveToThread(node->thread()); } else { m_progressUpdater = 0; } } KisProcessingVisitor::ProgressHelper::~ProgressHelper() { if (m_progressUpdater) { m_progressUpdater->deleteLater(); } } KoUpdater* KisProcessingVisitor::ProgressHelper::updater() const { QMutexLocker l(&m_progressMutex); return m_progressUpdater ? m_progressUpdater->startSubtask() : 0; } KisProcessingVisitor::~KisProcessingVisitor() { } + +KUndo2Command *KisProcessingVisitor::createInitCommand() +{ + return 0; +} diff --git a/libs/image/kis_processing_visitor.h b/libs/image/kis_processing_visitor.h index 6d3fe84369..4c5114897b 100644 --- a/libs/image/kis_processing_visitor.h +++ b/libs/image/kis_processing_visitor.h @@ -1,79 +1,87 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_PROCESSING_VISITOR_H #define __KIS_PROCESSING_VISITOR_H #include "kritaimage_export.h" #include "kis_shared.h" #include class KisNode; class KoUpdater; class KoProgressUpdater; class KisUndoAdapter; class KisPaintLayer; class KisGroupLayer; class KisAdjustmentLayer; class KisExternalLayer; class KisCloneLayer; class KisFilterMask; class KisTransformMask; class KisTransparencyMask; class KisSelectionMask; class KisGeneratorLayer; class KisColorizeMask; +class KUndo2Command; /** * A visitor that processes a single layer; it does not recurse into the * layer's children. Classes inheriting KisProcessingVisitor must not * emit signals or ask the image to update the projection. */ class KRITAIMAGE_EXPORT KisProcessingVisitor : public KisShared { public: virtual ~KisProcessingVisitor(); virtual void visit(KisNode *node, KisUndoAdapter *undoAdapter) = 0; virtual void visit(KisPaintLayer *layer, KisUndoAdapter *undoAdapter) = 0; virtual void visit(KisGroupLayer *layer, KisUndoAdapter *undoAdapter) = 0; virtual void visit(KisAdjustmentLayer *layer, KisUndoAdapter *undoAdapter) = 0; virtual void visit(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) = 0; virtual void visit(KisGeneratorLayer *layer, KisUndoAdapter *undoAdapter) = 0; virtual void visit(KisCloneLayer *layer, KisUndoAdapter *undoAdapter) = 0; virtual void visit(KisFilterMask *mask, KisUndoAdapter *undoAdapter) = 0; virtual void visit(KisTransformMask *mask, KisUndoAdapter *undoAdapter) = 0; virtual void visit(KisTransparencyMask *mask, KisUndoAdapter *undoAdapter) = 0; virtual void visit(KisColorizeMask *mask, KisUndoAdapter *undoAdapter) = 0; virtual void visit(KisSelectionMask *mask, KisUndoAdapter *undoAdapter) = 0; + /** + * Create a command that initializes the processing visitor before running + * on all the layers. The command is executed sequentially, non-exclusively + * on the image by applicator. + */ + virtual KUndo2Command* createInitCommand(); + public: class KRITAIMAGE_EXPORT ProgressHelper { public: ProgressHelper(const KisNode *node); ~ProgressHelper(); KoUpdater* updater() const; private: KoProgressUpdater *m_progressUpdater; mutable QMutex m_progressMutex; }; }; #endif /* __KIS_PROCESSING_VISITOR_H */ diff --git a/libs/image/kis_selection_mask.cpp b/libs/image/kis_selection_mask.cpp index 7f2a51dac1..69caeaa693 100644 --- a/libs/image/kis_selection_mask.cpp +++ b/libs/image/kis_selection_mask.cpp @@ -1,323 +1,324 @@ /* * 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_selection_mask.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_selection.h" #include #include #include #include "kis_fill_painter.h" #include #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_pixel_selection.h" #include "kis_undo_adapter.h" #include #include #include "kis_thread_safe_signal_compressor.h" #include "kis_layer_properties_icons.h" #include "kis_cached_paint_device.h" #include "kis_image_config.h" #include "KisImageConfigNotifier.h" struct Q_DECL_HIDDEN KisSelectionMask::Private { public: Private(KisSelectionMask *_q) : q(_q) , updatesCompressor(0) , maskColor(Qt::green, KoColorSpaceRegistry::instance()->rgb8()) {} KisSelectionMask *q; KisImageWSP image; KisCachedPaintDevice paintDeviceCache; KisCachedSelection cachedSelection; KisThreadSafeSignalCompressor *updatesCompressor; KoColor maskColor; void slotSelectionChangedCompressed(); void slotConfigChanged(); }; KisSelectionMask::KisSelectionMask(KisImageWSP image) : KisEffectMask() , m_d(new Private(this)) { setName("selection"); setActive(false); + setSupportsLodMoves(false); m_d->image = image; m_d->updatesCompressor = new KisThreadSafeSignalCompressor(50, KisSignalCompressor::FIRST_ACTIVE); connect(m_d->updatesCompressor, SIGNAL(timeout()), SLOT(slotSelectionChangedCompressed())); this->moveToThread(image->thread()); connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); m_d->slotConfigChanged(); } KisSelectionMask::KisSelectionMask(const KisSelectionMask& rhs) : KisEffectMask(rhs) , m_d(new Private(this)) { m_d->image = rhs.image(); m_d->updatesCompressor = new KisThreadSafeSignalCompressor(300, KisSignalCompressor::POSTPONE); connect(m_d->updatesCompressor, SIGNAL(timeout()), SLOT(slotSelectionChangedCompressed())); this->moveToThread(m_d->image->thread()); connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); m_d->slotConfigChanged(); } KisSelectionMask::~KisSelectionMask() { m_d->updatesCompressor->deleteLater(); delete m_d; } QIcon KisSelectionMask::icon() const { return KisIconUtils::loadIcon("selectionMask"); } void KisSelectionMask::mergeInMaskInternal(KisPaintDeviceSP projection, KisSelectionSP effectiveSelection, const QRect &applyRect, const QRect &preparedNeedRect, KisNode::PositionToFilthy maskPos) const { Q_UNUSED(maskPos); Q_UNUSED(preparedNeedRect); if (!effectiveSelection) return; { KisSelectionSP mainMaskSelection = this->selection(); if (mainMaskSelection && (!mainMaskSelection->isVisible() || mainMaskSelection->pixelSelection()->defaultBounds()->externalFrameActive())) { return; } } KisPaintDeviceSP fillDevice = m_d->paintDeviceCache.getDevice(projection); fillDevice->setDefaultPixel(m_d->maskColor); const QRect selectionExtent = effectiveSelection->selectedRect(); if (selectionExtent.contains(applyRect) || selectionExtent.intersects(applyRect)) { KisSelectionSP invertedSelection = m_d->cachedSelection.getSelection(); invertedSelection->pixelSelection()->makeCloneFromRough(effectiveSelection->pixelSelection(), applyRect); invertedSelection->pixelSelection()->invert(); KisPainter gc(projection); gc.setSelection(invertedSelection); gc.bitBlt(applyRect.topLeft(), fillDevice, applyRect); m_d->cachedSelection.putSelection(invertedSelection); } else { KisPainter gc(projection); gc.bitBlt(applyRect.topLeft(), fillDevice, applyRect); } m_d->paintDeviceCache.putDevice(fillDevice); } bool KisSelectionMask::paintsOutsideSelection() const { return true; } void KisSelectionMask::setSelection(KisSelectionSP selection) { if (selection) { KisEffectMask::setSelection(selection); } else { KisEffectMask::setSelection(new KisSelection()); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->alpha8(); KisFillPainter gc(KisPaintDeviceSP(this->selection()->pixelSelection().data())); gc.fillRect(image()->bounds(), KoColor(Qt::white, cs), MAX_SELECTED); gc.end(); } setDirty(); } KisImageWSP KisSelectionMask::image() const { return m_d->image; } bool KisSelectionMask::accept(KisNodeVisitor &v) { return v.visit(this); } void KisSelectionMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KisBaseNode::PropertyList KisSelectionMask::sectionModelProperties() const { KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties(); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::selectionActive, active()); return l; } void KisSelectionMask::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisEffectMask::setSectionModelProperties(properties); setActive(properties.at(2).state.toBool()); } void KisSelectionMask::setVisible(bool visible, bool isLoading) { const bool oldVisible = this->visible(false); setNodeProperty("visible", visible); if (!isLoading && visible != oldVisible) { if (selection()) selection()->setVisible(visible); emit(visibilityChanged(visible)); } } bool KisSelectionMask::active() const { return nodeProperties().boolProperty("active", true); } void KisSelectionMask::setActive(bool active) { KisImageWSP image = this->image(); KisLayerSP parentLayer = qobject_cast(parent().data()); if (active && parentLayer) { KisSelectionMaskSP activeMask = parentLayer->selectionMask(); if (activeMask && activeMask != this) { activeMask->setActive(false); } } const bool oldActive = this->active(); setNodeProperty("active", active); /** * WARNING: we have a direct link to the image here, but we * must not use it for notification until we are a part of * the nore graph! Notifications should be emitted iff we * have graph listener link set up. */ if (graphListener() && image && oldActive != active) { baseNodeChangedCallback(); image->undoAdapter()->emitSelectionChanged(); } } QRect KisSelectionMask::needRect(const QRect &rect, KisNode::PositionToFilthy pos) const { Q_UNUSED(pos); // selection masks just add an overlay, so the needed rect is simply passed through return rect; } QRect KisSelectionMask::changeRect(const QRect &rect, KisNode::PositionToFilthy pos) const { Q_UNUSED(pos); // selection masks just add an overlay, so the changed rect is simply passed through return rect; } QRect KisSelectionMask::extent() const { // since mask overlay is inverted, the mask paints over // the entire image bounds QRect resultRect; KisSelectionSP selection = this->selection(); if (selection) { resultRect = selection->pixelSelection()->defaultBounds()->bounds(); } else if (KisNodeSP parent = this->parent()) { KisPaintDeviceSP dev = parent->projection(); if (dev) { resultRect = dev->defaultBounds()->bounds(); } } return resultRect; } QRect KisSelectionMask::exactBounds() const { return extent(); } void KisSelectionMask::notifySelectionChangedCompressed() { m_d->updatesCompressor->start(); } void KisSelectionMask::flattenSelectionProjection(KisSelectionSP selection, const QRect &dirtyRect) const { Q_UNUSED(selection); Q_UNUSED(dirtyRect); } void KisSelectionMask::Private::slotSelectionChangedCompressed() { KisSelectionSP currentSelection = q->selection(); if (!currentSelection) return; currentSelection->notifySelectionChanged(); } void KisSelectionMask::Private::slotConfigChanged() { const KoColorSpace *cs = image ? image->colorSpace() : KoColorSpaceRegistry::instance()->rgb8(); KisImageConfig cfg(true); maskColor = KoColor(cfg.selectionOverlayMaskColor(), cs); if (image && image->overlaySelectionMask() == q) { q->setDirty(); } } #include "moc_kis_selection_mask.cpp" diff --git a/libs/image/processing/KisSelectionBasedProcessingHelper.cpp b/libs/image/processing/KisSelectionBasedProcessingHelper.cpp new file mode 100644 index 0000000000..17fcb77bab --- /dev/null +++ b/libs/image/processing/KisSelectionBasedProcessingHelper.cpp @@ -0,0 +1,91 @@ +#include "KisSelectionBasedProcessingHelper.h" + +#include "kis_paint_device.h" +#include "kis_painter.h" +#include "kis_selection.h" +#include "kis_transaction_based_command.h" +#include "kis_transaction.h" +#include "kis_undo_adapter.h" + +KisSelectionBasedProcessingHelper::KisSelectionBasedProcessingHelper(KisSelectionSP selection, Functor func) + : m_selection(selection), + m_func(func) +{ +} + +void KisSelectionBasedProcessingHelper::setSelection(KisSelectionSP selection) +{ + m_selection = selection; +} + +KUndo2Command *KisSelectionBasedProcessingHelper::createInitCommand(Functor func) +{ + if (!m_selection) return 0; + + struct ProcessSelectionCommand : KisTransactionBasedCommand { + ProcessSelectionCommand(KisSelectionSP selection, + KisSelectionSP cutSelection, + std::function func) + : m_selection(selection), + m_cutSelection(cutSelection), + m_func(func) + { + } + + KUndo2Command* paint() { + m_cutSelection->pixelSelection()->makeCloneFromRough(m_selection->pixelSelection(), m_selection->selectedRect()); + + KisTransaction t(m_selection->pixelSelection()); + m_func(m_selection->pixelSelection()); + + return t.endAndTake(); + } + + KisSelectionSP m_selection; + KisSelectionSP m_cutSelection; + Functor m_func; + }; + + m_cutSelection = new KisSelection(); + return new ProcessSelectionCommand(m_selection, m_cutSelection, func); +} + +KUndo2Command *KisSelectionBasedProcessingHelper::createInitCommand() +{ + return createInitCommand(m_func); +} + +void KisSelectionBasedProcessingHelper::transformPaintDevice(KisPaintDeviceSP device, KisUndoAdapter *undoAdapter) +{ + transformPaintDevice(device, undoAdapter, m_func); +} + +void KisSelectionBasedProcessingHelper::transformPaintDevice(KisPaintDeviceSP device, KisUndoAdapter *undoAdapter, Functor func) +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(!!m_selection == !!m_cutSelection); + + if (m_selection && m_cutSelection) { + // we have already processed the selection in the init command so try to skip it + if (device != static_cast(m_selection->pixelSelection().data())) { + KisTransaction transaction(device); + + const QRect cutBounds = m_cutSelection->selectedExactRect(); + const QRect pasteBounds = m_selection->selectedExactRect(); + + + KisPaintDeviceSP tempDev = new KisPaintDevice(device->colorSpace()); + tempDev->makeCloneFromRough(device, cutBounds); + + func(tempDev); + + device->clearSelection(m_cutSelection); + KisPainter::copyAreaOptimized(pasteBounds.topLeft(), tempDev, device, pasteBounds, m_selection); + transaction.commit(undoAdapter); + + } + } else { + KisTransaction transaction(device); + func(device); + transaction.commit(undoAdapter); + } +} diff --git a/libs/image/processing/KisSelectionBasedProcessingHelper.h b/libs/image/processing/KisSelectionBasedProcessingHelper.h new file mode 100644 index 0000000000..7bb0b8dc46 --- /dev/null +++ b/libs/image/processing/KisSelectionBasedProcessingHelper.h @@ -0,0 +1,37 @@ +#ifndef KISSELECTIONBASEDPROCESSINGHELPER_H +#define KISSELECTIONBASEDPROCESSINGHELPER_H + +#include "kritaimage_export.h" +#include "kis_types.h" + +#include +#include + +class KisUndoAdapter; + + +class KRITAIMAGE_EXPORT KisSelectionBasedProcessingHelper +{ +public: + using Functor = std::function; +public: + KisSelectionBasedProcessingHelper(KisSelectionSP selection, Functor func); + + void setSelection(KisSelectionSP selection); + + KUndo2Command *createInitCommand(); + KUndo2Command *createInitCommand(Functor func); + + + void transformPaintDevice(KisPaintDeviceSP device, KisUndoAdapter *undoAdapter); + + void transformPaintDevice(KisPaintDeviceSP device, KisUndoAdapter *undoAdapter, Functor func); + + +private: + KisSelectionSP m_selection; + KisSelectionSP m_cutSelection; + Functor m_func; +}; + +#endif // KISSELECTIONBASEDPROCESSINGHELPER_H diff --git a/libs/image/processing/kis_mirror_processing_visitor.cpp b/libs/image/processing/kis_mirror_processing_visitor.cpp index 2dc840fba8..814cc2fe24 100644 --- a/libs/image/processing/kis_mirror_processing_visitor.cpp +++ b/libs/image/processing/kis_mirror_processing_visitor.cpp @@ -1,80 +1,97 @@ /* * 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_mirror_processing_visitor.h" #include "kis_paint_device.h" #include "kis_transaction.h" #include "kis_node.h" #include "kis_image.h" +#include "kis_painter.h" #include "kis_transform_worker.h" #include "lazybrush/kis_colorize_mask.h" #include "processing/kis_transform_processing_visitor.h" +#include "commands_new/kis_transaction_based_command.h" +#include + KisMirrorProcessingVisitor::KisMirrorProcessingVisitor(const QRect &bounds, Qt::Orientation orientation) - : m_bounds(bounds), m_orientation(orientation) + : m_bounds(bounds), + m_orientation(orientation), + m_selectionHelper(0, std::bind(&KisMirrorProcessingVisitor::mirrorDevice, this, std::placeholders::_1)) { + m_axis = m_orientation == Qt::Horizontal ? + m_bounds.x() + 0.5 * m_bounds.width() : + m_bounds.y() + 0.5 * m_bounds.height(); } -void KisMirrorProcessingVisitor::transformPaintDevice(KisPaintDeviceSP device, KisUndoAdapter *undoAdapter) +KisMirrorProcessingVisitor::KisMirrorProcessingVisitor(KisSelectionSP selection, Qt::Orientation orientation) + : KisMirrorProcessingVisitor(selection->selectedExactRect(), orientation) { - KisTransaction transaction(device); + m_selectionHelper.setSelection(selection); +} - qreal axis = m_orientation == Qt::Horizontal ? - m_bounds.x() + 0.5 * m_bounds.width() : - m_bounds.y() + 0.5 * m_bounds.height(); +KUndo2Command *KisMirrorProcessingVisitor::createInitCommand() +{ + return m_selectionHelper.createInitCommand(); +} - KisTransformWorker::mirror(device, axis, m_orientation); - transaction.commit(undoAdapter); +void KisMirrorProcessingVisitor::mirrorDevice(KisPaintDeviceSP device) +{ + KisTransformWorker::mirror(device, m_axis, m_orientation); +} + +void KisMirrorProcessingVisitor::transformPaintDevice(KisPaintDeviceSP device, KisUndoAdapter *undoAdapter) +{ + m_selectionHelper.transformPaintDevice(device, undoAdapter); } void KisMirrorProcessingVisitor::visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter) { transformPaintDevice(node->paintDevice(), undoAdapter); - } void KisMirrorProcessingVisitor::visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) { if (m_orientation == Qt::Horizontal) { KisTransformProcessingVisitor visitor(-1.0, 1.0, 0.0, 0.0, QPointF(), 0.0, m_bounds.width(), 0.0, 0); visitor.visit(layer, undoAdapter); } else { KisTransformProcessingVisitor visitor(1.0, -1.0, 0.0, 0.0, QPointF(), 0.0, 0.0, m_bounds.height(), 0); visitor.visit(layer, undoAdapter); } } void KisMirrorProcessingVisitor::visitColorizeMask(KisColorizeMask *node, KisUndoAdapter *undoAdapter) { QVector devices = node->allPaintDevices(); Q_FOREACH (KisPaintDeviceSP device, devices) { transformPaintDevice(device, undoAdapter); } } diff --git a/libs/image/processing/kis_mirror_processing_visitor.h b/libs/image/processing/kis_mirror_processing_visitor.h index e8f294070c..e09f64c10e 100644 --- a/libs/image/processing/kis_mirror_processing_visitor.h +++ b/libs/image/processing/kis_mirror_processing_visitor.h @@ -1,45 +1,55 @@ /* * 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_MIRROR_PROCESSING_VISITOR_H #define __KIS_MIRROR_PROCESSING_VISITOR_H #include "kis_simple_processing_visitor.h" #include #include "kis_types.h" +#include "KisSelectionBasedProcessingHelper.h" + class KRITAIMAGE_EXPORT KisMirrorProcessingVisitor : public KisSimpleProcessingVisitor { public: KisMirrorProcessingVisitor(const QRect &bounds, Qt::Orientation orientation); + KisMirrorProcessingVisitor(KisSelectionSP selection, Qt::Orientation orientation); private: void visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter) override; void visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) override; void visitColorizeMask(KisColorizeMask *node, KisUndoAdapter *undoAdapter) override; + KUndo2Command* createInitCommand() override; + + void mirrorDevice(KisPaintDeviceSP device); + private: void transformPaintDevice(KisPaintDeviceSP device, KisUndoAdapter *undoAdapter); QRect m_bounds; Qt::Orientation m_orientation; + qreal m_axis = 0.0; + + KisSelectionBasedProcessingHelper m_selectionHelper; }; #endif /* __KIS_MIRROR_PROCESSING_VISITOR_H */ diff --git a/libs/image/processing/kis_transform_processing_visitor.cpp b/libs/image/processing/kis_transform_processing_visitor.cpp index d62099c536..0e6f61a9cd 100644 --- a/libs/image/processing/kis_transform_processing_visitor.cpp +++ b/libs/image/processing/kis_transform_processing_visitor.cpp @@ -1,220 +1,252 @@ /* * 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_transform_processing_visitor.h" #include "klocalizedstring.h" #include #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_selection.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_adjustment_layer.h" #include "generator/kis_generator_layer.h" #include "kis_transparency_mask.h" #include "kis_filter_mask.h" #include "kis_transform_mask.h" #include "kis_selection_mask.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_external_layer_iface.h" #include "kis_transaction.h" #include "kis_undo_adapter.h" #include "kis_transform_worker.h" #include "commands_new/kis_node_move_command2.h" #include "kis_do_something_command.h" KisTransformProcessingVisitor:: KisTransformProcessingVisitor(qreal xscale, qreal yscale, qreal xshear, qreal yshear, const QPointF &shearOrigin, qreal angle, qint32 tx, qint32 ty, KisFilterStrategy *filter, const QTransform &shapesCorrection) : m_sx(xscale), m_sy(yscale) , m_tx(tx), m_ty(ty) , m_shearx(xshear), m_sheary(yshear) , m_shearOrigin(shearOrigin) , m_filter(filter) , m_angle(angle) , m_shapesCorrection(shapesCorrection) + , m_selectionHelper(0, KisSelectionBasedProcessingHelper::Functor()) { } +void KisTransformProcessingVisitor::setSelection(KisSelectionSP selection) +{ + m_selectionHelper.setSelection(selection); +} + +KUndo2Command *KisTransformProcessingVisitor::createInitCommand() +{ + return m_selectionHelper.createInitCommand( + std::bind(&KisTransformProcessingVisitor::transformOneDevice, + this, + std::placeholders::_1, + (KoUpdater*)0)); +} + void KisTransformProcessingVisitor::visit(KisNode *node, KisUndoAdapter *undoAdapter) { Q_UNUSED(node); Q_UNUSED(undoAdapter); } void KisTransformProcessingVisitor::visit(KisPaintLayer *layer, KisUndoAdapter *undoAdapter) { transformPaintDevice(layer->paintDevice(), undoAdapter, ProgressHelper(layer)); transformClones(layer, undoAdapter); } void KisTransformProcessingVisitor::visit(KisGroupLayer *layer, KisUndoAdapter *undoAdapter) { using namespace KisDoSomethingCommandOps; undoAdapter->addCommand(new KisDoSomethingCommand(layer, false)); undoAdapter->addCommand(new KisDoSomethingCommand(layer, true)); transformClones(layer, undoAdapter); } void KisTransformProcessingVisitor::visit(KisAdjustmentLayer *layer, KisUndoAdapter *undoAdapter) { using namespace KisDoSomethingCommandOps; undoAdapter->addCommand(new KisDoSomethingCommand(layer, false)); transformSelection(layer->internalSelection(), undoAdapter, ProgressHelper(layer)); undoAdapter->addCommand(new KisDoSomethingCommand(layer, true)); transformClones(layer, undoAdapter); } void KisTransformProcessingVisitor::visit(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) { KisTransformWorker tw(layer->projection(), m_sx, m_sy, m_shearx, m_sheary, m_shearOrigin.x(), m_shearOrigin.y(), m_angle, m_tx, m_ty, 0, m_filter); KUndo2Command* command = layer->transform(tw.transform() * m_shapesCorrection); if(command) { undoAdapter->addCommand(command); } transformClones(layer, undoAdapter); } void KisTransformProcessingVisitor::visit(KisGeneratorLayer *layer, KisUndoAdapter *undoAdapter) { using namespace KisDoSomethingCommandOps; undoAdapter->addCommand(new KisDoSomethingCommand(layer, false)); transformSelection(layer->internalSelection(), undoAdapter, ProgressHelper(layer)); undoAdapter->addCommand(new KisDoSomethingCommand(layer, true)); transformClones(layer, undoAdapter); } void KisTransformProcessingVisitor::visit(KisCloneLayer *layer, KisUndoAdapter *undoAdapter) { transformClones(layer, undoAdapter); } void KisTransformProcessingVisitor::visit(KisFilterMask *mask, KisUndoAdapter *undoAdapter) { transformSelection(mask->selection(), undoAdapter, ProgressHelper(mask)); } void KisTransformProcessingVisitor::visit(KisTransformMask *mask, KisUndoAdapter *undoAdapter) { Q_UNUSED(mask); Q_UNUSED(undoAdapter); warnKrita << "WARNING: transformation of the transform mask is not implemented"; } void KisTransformProcessingVisitor::visit(KisTransparencyMask *mask, KisUndoAdapter *undoAdapter) { transformSelection(mask->selection(), undoAdapter, ProgressHelper(mask)); } void KisTransformProcessingVisitor::visit(KisSelectionMask *mask, KisUndoAdapter *undoAdapter) { transformSelection(mask->selection(), undoAdapter, ProgressHelper(mask)); } void KisTransformProcessingVisitor::visit(KisColorizeMask *mask, KisUndoAdapter *undoAdapter) { QVector devices = mask->allPaintDevices(); Q_FOREACH (KisPaintDeviceSP device, devices) { transformPaintDevice(device, undoAdapter, ProgressHelper(mask)); } } void KisTransformProcessingVisitor::transformClones(KisLayer *layer, KisUndoAdapter *undoAdapter) { QList clones = layer->registeredClones(); Q_FOREACH (KisCloneLayerSP clone, clones) { // we have just casted an object from a weak pointer, // so check validity first if(!clone) continue; KisTransformWorker tw(clone->projection(), m_sx, m_sy, m_shearx, m_sheary, m_shearOrigin.x(), m_shearOrigin.y(), m_angle, m_tx, m_ty, 0, m_filter); QTransform trans = tw.transform(); QTransform offsetTrans = QTransform::fromTranslate(clone->x(), clone->y()); QTransform newTrans = trans.inverted() * offsetTrans * trans; QPoint oldPos(clone->x(), clone->y()); QPoint newPos(newTrans.dx(), newTrans.dy()); KUndo2Command *command = new KisNodeMoveCommand2(clone, oldPos, newPos); undoAdapter->addCommand(command); } } void KisTransformProcessingVisitor::transformPaintDevice(KisPaintDeviceSP device, KisUndoAdapter *adapter, const ProgressHelper &helper) { + m_selectionHelper.transformPaintDevice(device, + adapter, + std::bind(&KisTransformProcessingVisitor::transformOneDevice, + this, + std::placeholders::_1, + helper.updater())); + + + return; + KisTransaction transaction(kundo2_i18n("Transform Layer"), device); KisTransformWorker tw(device, m_sx, m_sy, m_shearx, m_sheary, m_shearOrigin.x(), m_shearOrigin.y(), m_angle, m_tx, m_ty, helper.updater(), m_filter); tw.run(); transaction.commit(adapter); } -void KisTransformProcessingVisitor::transformSelection(KisSelectionSP selection, KisUndoAdapter *adapter, const ProgressHelper &helper) +void KisTransformProcessingVisitor::transformOneDevice(KisPaintDeviceSP device, + KoUpdater *updater) { - if(selection->hasPixelSelection()) { - transformPaintDevice(selection->pixelSelection(), adapter, helper); - } + KisTransformWorker tw(device, m_sx, m_sy, m_shearx, m_sheary, + m_shearOrigin.x(), m_shearOrigin.y(), + m_angle, m_tx, m_ty, updater, + m_filter); + tw.run(); +} +void KisTransformProcessingVisitor::transformSelection(KisSelectionSP selection, KisUndoAdapter *adapter, const ProgressHelper &helper) +{ if (selection->hasShapeSelection()) { KisTransformWorker tw(selection->projection(), m_sx, m_sy, m_shearx, m_sheary, m_shearOrigin.x(), m_shearOrigin.y(), m_angle, m_tx, m_ty, 0, m_filter); KUndo2Command* command = selection->shapeSelection()->transform(tw.transform() * m_shapesCorrection); if (command) { adapter->addCommand(command); } + } else { + transformPaintDevice(selection->pixelSelection(), adapter, helper); } selection->updateProjection(); } - diff --git a/libs/image/processing/kis_transform_processing_visitor.h b/libs/image/processing/kis_transform_processing_visitor.h index e72fa64bbd..d8b8a66e6a 100644 --- a/libs/image/processing/kis_transform_processing_visitor.h +++ b/libs/image/processing/kis_transform_processing_visitor.h @@ -1,70 +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. */ #ifndef __KIS_TRANSFORM_PROCESSING_VISITOR_H #define __KIS_TRANSFORM_PROCESSING_VISITOR_H #include "kis_processing_visitor.h" - +#include "KisSelectionBasedProcessingHelper.h" #include #include #include class KisFilterStrategy; class KRITAIMAGE_EXPORT KisTransformProcessingVisitor : public KisProcessingVisitor { public: KisTransformProcessingVisitor(qreal xscale, qreal yscale, qreal xshear, qreal yshear, const QPointF &shearOrigin, qreal angle, qint32 tx, qint32 ty, KisFilterStrategy *filter, const QTransform &shapesCorrection = QTransform()); + void setSelection(KisSelectionSP selection); + KUndo2Command *createInitCommand(); + + void visit(KisNode *node, KisUndoAdapter *undoAdapter) override; void visit(KisPaintLayer *layer, KisUndoAdapter *undoAdapter) override; void visit(KisGroupLayer *layer, KisUndoAdapter *undoAdapter) override; void visit(KisAdjustmentLayer *layer, KisUndoAdapter *undoAdapter) override; void visit(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) override; void visit(KisGeneratorLayer *layer, KisUndoAdapter *undoAdapter) override; void visit(KisCloneLayer *layer, KisUndoAdapter *undoAdapter) override; void visit(KisFilterMask *mask, KisUndoAdapter *undoAdapter) override; void visit(KisTransformMask *mask, KisUndoAdapter *undoAdapter) override; void visit(KisTransparencyMask *mask, KisUndoAdapter *undoAdapter) override; void visit(KisSelectionMask *mask, KisUndoAdapter *undoAdapter) override; void visit(KisColorizeMask *mask, KisUndoAdapter *undoAdapter) override; private: void transformClones(KisLayer *layer, KisUndoAdapter *undoAdapter); void transformPaintDevice(KisPaintDeviceSP device, KisUndoAdapter *adapter, const ProgressHelper &helper); void transformSelection(KisSelectionSP selection, KisUndoAdapter *adapter, const ProgressHelper &helper); + void transformOneDevice(KisPaintDeviceSP device, KoUpdater *updater); + private: qreal m_sx, m_sy; qint32 m_tx, m_ty; qreal m_shearx, m_sheary; QPointF m_shearOrigin; KisFilterStrategy *m_filter; qreal m_angle; QTransform m_shapesCorrection; + KisSelectionBasedProcessingHelper m_selectionHelper; }; #endif /* __KIS_TRANSFORM_PROCESSING_VISITOR_H */ diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt index 8304ebc18d..d40e0e13be 100644 --- a/libs/image/tests/CMakeLists.txt +++ b/libs/image/tests/CMakeLists.txt @@ -1,159 +1,157 @@ # cmake in some versions for some not yet known reasons fails to run automoc # on random targets (changing target names already has an effect) # As temporary workaround skipping build of tests on these versions for now # See https://mail.kde.org/pipermail/kde-buildsystem/2015-June/010819.html # extend range of affected cmake versions as needed if(NOT ${CMAKE_VERSION} VERSION_LESS 3.1.3 AND NOT ${CMAKE_VERSION} VERSION_GREATER 3.2.3) message(WARNING "Skipping krita/image/tests, CMake in at least versions 3.1.3 - 3.2.3 seems to have a problem with automoc. \n(FRIENDLY REMINDER: PLEASE DON'T BREAK THE TESTS!)") set (HAVE_FAILING_CMAKE TRUE) else() set (HAVE_FAILING_CMAKE FALSE) endif() include_directories( - ${CMAKE_SOURCE_DIR}/libs/image/metadata ${CMAKE_BINARY_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/ ${CMAKE_SOURCE_DIR}/libs/image/brushengine ${CMAKE_SOURCE_DIR}/libs/image/tiles3 ${CMAKE_SOURCE_DIR}/libs/image/tiles3/swap ${CMAKE_SOURCE_DIR}/sdk/tests ) include_Directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(HAVE_VC) include_directories(${Vc_INCLUDE_DIR}) endif() include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() set(KisRandomGeneratorDemoSources kis_random_generator_demo.cpp kimageframe.cpp) ki18n_wrap_ui(KisRandomGeneratorDemoSources kis_random_generator_demo.ui) add_executable(KisRandomGeneratorDemo ${KisRandomGeneratorDemoSources}) target_link_libraries(KisRandomGeneratorDemo kritaimage) ecm_mark_as_test(KisRandomGeneratorDemo) ecm_add_tests( kis_base_node_test.cpp kis_fast_math_test.cpp kis_node_test.cpp kis_node_facade_test.cpp kis_fixed_paint_device_test.cpp kis_layer_test.cpp kis_effect_mask_test.cpp kis_iterator_test.cpp kis_painter_test.cpp kis_selection_test.cpp kis_count_visitor_test.cpp kis_projection_test.cpp kis_properties_configuration_test.cpp kis_transaction_test.cpp kis_pixel_selection_test.cpp kis_group_layer_test.cpp kis_paint_layer_test.cpp kis_adjustment_layer_test.cpp kis_annotation_test.cpp kis_change_profile_visitor_test.cpp kis_clone_layer_test.cpp kis_colorspace_convert_visitor_test.cpp kis_convolution_painter_test.cpp kis_crop_processing_visitor_test.cpp kis_processing_applicator_test.cpp kis_datamanager_test.cpp kis_fill_painter_test.cpp kis_filter_configuration_test.cpp kis_filter_test.cpp kis_filter_processing_information_test.cpp kis_filter_registry_test.cpp kis_filter_strategy_test.cpp kis_gradient_painter_test.cpp kis_image_commands_test.cpp kis_image_test.cpp kis_image_signal_router_test.cpp kis_iterators_ng_test.cpp kis_iterator_benchmark.cpp kis_updater_context_test.cpp kis_simple_update_queue_test.cpp kis_stroke_test.cpp kis_simple_stroke_strategy_test.cpp kis_stroke_strategy_undo_command_based_test.cpp kis_strokes_queue_test.cpp kis_mask_test.cpp kis_math_toolbox_test.cpp kis_name_server_test.cpp kis_node_commands_test.cpp kis_node_graph_listener_test.cpp kis_node_visitor_test.cpp kis_paint_information_test.cpp kis_distance_information_test.cpp kis_paintop_test.cpp kis_pattern_test.cpp kis_selection_mask_test.cpp kis_shared_ptr_test.cpp kis_bsplines_test.cpp kis_warp_transform_worker_test.cpp kis_liquify_transform_worker_test.cpp kis_transparency_mask_test.cpp kis_types_test.cpp kis_vec_test.cpp kis_filter_config_widget_test.cpp kis_mask_generator_test.cpp kis_cubic_curve_test.cpp kis_fixed_point_maths_test.cpp kis_node_query_path_test.cpp kis_filter_weights_buffer_test.cpp kis_filter_weights_applicator_test.cpp kis_fill_interval_test.cpp kis_fill_interval_map_test.cpp kis_scanline_fill_test.cpp kis_psd_layer_style_test.cpp kis_layer_style_projection_plane_test.cpp kis_lod_capable_layer_offset_test.cpp kis_algebra_2d_test.cpp kis_marker_painter_test.cpp kis_lazy_brush_test.cpp kis_colorize_mask_test.cpp kis_mask_similarity_test.cpp KisMaskGeneratorBenchmark.cpp kis_layer_style_filter_environment_test.cpp kis_asl_parser_test.cpp KisPerStrokeRandomSourceTest.cpp KisWatershedWorkerTest.cpp kis_dom_utils_test.cpp kis_transform_worker_test.cpp kis_perspective_transform_worker_test.cpp kis_cs_conversion_test.cpp kis_processings_test.cpp kis_projection_leaf_test.cpp kis_histogram_test.cpp kis_onion_skin_compositor_test.cpp kis_paint_device_test.cpp kis_queues_progress_updater_test.cpp kis_image_animation_interface_test.cpp kis_walkers_test.cpp kis_update_scheduler_test.cpp kis_async_merger_test.cpp kis_cage_transform_worker_test.cpp - kis_meta_data_test.cpp kis_random_generator_test.cpp kis_keyframing_test.cpp kis_filter_mask_test.cpp LINK_LIBRARIES kritaimage Qt5::Test NAME_PREFIX "libs-image-" ) krita_add_broken_unit_tests( kis_transform_mask_test.cpp kis_layer_styles_test.cpp LINK_LIBRARIES kritaimage Qt5::Test NAME_PREFIX "libs-image-" ) diff --git a/libs/image/tests/kis_image_test.cpp b/libs/image/tests/kis_image_test.cpp index 8546bcf63e..cb1a52043b 100644 --- a/libs/image/tests/kis_image_test.cpp +++ b/libs/image/tests/kis_image_test.cpp @@ -1,1207 +1,1207 @@ /* * Copyright (c) 2005 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. */ #include "kis_image_test.h" #include #include #include #include #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "kis_selection.h" #include #include #include "kis_keyframe_channel.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "kis_annotation.h" #include "KisProofingConfiguration.h" #include "kis_undo_stores.h" #define IMAGE_WIDTH 128 #define IMAGE_HEIGHT 128 void KisImageTest::layerTests() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE_U8); image->addNode(layer); QVERIFY(image->rootLayer()->firstChild()->objectName() == layer->objectName()); } void KisImageTest::benchmarkCreation() { const QRect imageRect(0,0,3000,2000); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QList images; QList stores; QBENCHMARK { for (int i = 0; i < 10; i++) { stores << new KisSurrogateUndoStore(); } for (int i = 0; i < 10; i++) { KisImageSP image = new KisImage(stores.takeLast(), imageRect.width(), imageRect.height(), cs, "test image"); images << image; } } } #include "testutil.h" #include "kis_stroke_strategy.h" #include class ForbiddenLodStrokeStrategy : public KisStrokeStrategy { public: ForbiddenLodStrokeStrategy(std::function lodCallback) : m_lodCallback(lodCallback) { } KisStrokeStrategy* createLodClone(int levelOfDetail) override { Q_UNUSED(levelOfDetail); m_lodCallback(); return 0; } private: std::function m_lodCallback; }; void notifyVar(bool *value) { *value = true; } void KisImageTest::testBlockLevelOfDetail() { TestUtil::MaskParent p; QCOMPARE(p.image->currentLevelOfDetail(), 0); p.image->setDesiredLevelOfDetail(1); p.image->waitForDone(); QCOMPARE(p.image->currentLevelOfDetail(), 0); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } p.image->setLevelOfDetailBlocked(true); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(!lodCreated); } p.image->setLevelOfDetailBlocked(false); p.image->setDesiredLevelOfDetail(1); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } } void KisImageTest::testConvertImageColorSpace() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); KisPaintDeviceSP device1 = new KisPaintDevice(cs8); KisLayerSP paint1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration = filter->defaultConfiguration(); Q_ASSERT(configuration); KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); image->addNode(paint1, image->root()); image->addNode(blur1, image->root()); image->refreshGraph(); const KoColorSpace *cs16 = KoColorSpaceRegistry::instance()->rgb16(); image->lock(); image->convertImageColorSpace(cs16, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->unlock(); QVERIFY(*cs16 == *image->colorSpace()); QVERIFY(*cs16 == *image->root()->colorSpace()); QVERIFY(*cs16 == *paint1->colorSpace()); QVERIFY(*cs16 == *blur1->colorSpace()); QVERIFY(!image->root()->compositeOp()); QVERIFY(*cs16 == *paint1->compositeOp()->colorSpace()); QVERIFY(*cs16 == *blur1->compositeOp()->colorSpace()); image->refreshGraph(); } void KisImageTest::testGlobalSelection() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 0U); KisSelectionSP selection1 = new KisSelection(new KisDefaultBounds(image)); KisSelectionSP selection2 = new KisSelection(new KisDefaultBounds(image)); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->setGlobalSelection(selection2); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->reselectGlobalSelection(); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); // mixed deselecting/setting/reselecting image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); } void KisImageTest::testCloneImage() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisAnnotationSP annotation = new KisAnnotation("mytype", "mydescription", QByteArray()); image->addAnnotation(annotation); QVERIFY(image->annotation("mytype")); KisProofingConfigurationSP proofing = toQShared(new KisProofingConfiguration()); image->setProofingConfiguration(proofing); QVERIFY(image->proofingConfiguration()); const KoColor defaultColor(Qt::green, image->colorSpace()); image->setDefaultProjectionColor(defaultColor); QCOMPARE(image->defaultProjectionColor(), defaultColor); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); QVERIFY(TestUtil::findNode(image->root(), "layer1")); QVERIFY(TestUtil::findNode(image->root(), "layer2")); QUuid uuid1 = layer->uuid(); QUuid uuid2 = layer2->uuid(); { KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() != uuid1); QVERIFY(newLayer2->uuid() != uuid2); KisAnnotationSP newAnnotation = newImage->annotation("mytype"); QVERIFY(newAnnotation); QVERIFY(newAnnotation != annotation); KisProofingConfigurationSP newProofing = newImage->proofingConfiguration(); QVERIFY(newProofing); QVERIFY(newProofing != proofing); QCOMPARE(newImage->defaultProjectionColor(), defaultColor); } { KisImageSP newImage = image->clone(true); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() == uuid1); QVERIFY(newLayer2->uuid() == uuid2); } } void KisImageTest::testLayerComposition() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisLayerComposition comp(image, "comp 1"); comp.store(); layer2->setVisible(false); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); KisLayerComposition comp2(image, "comp 2"); comp2.store(); KisLayerCompositionSP comp3 = toQShared(new KisLayerComposition(image, "comp 3")); comp3->store(); image->addComposition(comp3); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); comp2.apply(); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp1(comp, newImage); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp2(comp2, newImage); newComp2.apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); QVERIFY(!newImage->compositions().isEmpty()); KisLayerCompositionSP newComp3 = newImage->compositions().first(); newComp3->apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); } #include "kis_transparency_mask.h" #include "kis_psd_layer_style.h" struct FlattenTestImage { FlattenTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; layer1 = p.layer; layer5 = new KisPaintLayer(p.image, "paint5", 0.4 * OPACITY_OPAQUE_U8); layer5->disableAlphaChannel(true); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); tmask = new KisTransparencyMask(); // check channel flags // make addition composite op group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); layer4 = new KisPaintLayer(p.image, "paint4", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); layer7 = new KisPaintLayer(p.image, "paint7", OPACITY_OPAQUE_U8); layer8 = new KisPaintLayer(p.image, "paint8", OPACITY_OPAQUE_U8); layer7->setCompositeOpId(COMPOSITE_ADD); layer8->setCompositeOpId(COMPOSITE_ADD); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect tmaskRect(200,200,100,100); QRect rect3(400, 100, 100, 100); QRect rect4(500, 100, 100, 100); QRect rect5(50, 50, 100, 100); QRect rect6(50, 250, 100, 100); QRect rect7(50, 350, 50, 50); QRect rect8(50, 400, 50, 50); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, p.image->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, p.image->colorSpace())); tmask->testingInitSelection(tmaskRect, layer2); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, p.image->colorSpace())); layer4->paintDevice()->fill(rect4, KoColor(Qt::yellow, p.image->colorSpace())); layer5->paintDevice()->fill(rect5, KoColor(Qt::green, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::cyan, p.image->colorSpace())); layer7->paintDevice()->fill(rect7, KoColor(Qt::red, p.image->colorSpace())); layer8->paintDevice()->fill(rect8, KoColor(Qt::green, p.image->colorSpace())); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(10.0); style->dropShadow()->setSpread(80.0); style->dropShadow()->setSize(10); style->dropShadow()->setNoise(0); style->dropShadow()->setKnocksOut(false); style->dropShadow()->setOpacity(80.0); layer2->setLayerStyle(style); layer2->setCompositeOpId(COMPOSITE_ADD); group1->setCompositeOpId(COMPOSITE_ADD); p.image->addNode(layer5); p.image->addNode(layer2); p.image->addNode(tmask, layer2); p.image->addNode(group1); p.image->addNode(layer3, group1); p.image->addNode(layer4, group1); p.image->addNode(layer6); p.image->addNode(layer7); p.image->addNode(layer8); p.image->initialRefreshGraph(); // dbgKrita << ppVar(layer1->exactBounds()); // dbgKrita << ppVar(layer5->exactBounds()); // dbgKrita << ppVar(layer2->exactBounds()); // dbgKrita << ppVar(group1->exactBounds()); // dbgKrita << ppVar(layer3->exactBounds()); // dbgKrita << ppVar(layer4->exactBounds()); TestUtil::ReferenceImageChecker chk("flatten", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisTransparencyMaskSP tmask; KisGroupLayerSP group1; KisPaintLayerSP layer3; KisPaintLayerSP layer4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; KisPaintLayerSP layer7; KisPaintLayerSP layer8; }; template KisLayerSP flattenLayerHelper(ContainerTest &p, KisLayerSP layer, bool nothingHappens = false) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); //p.image->flattenLayer(layer); KisLayerUtils::flattenLayer(p.image, layer); p.image->waitForDone(); if (nothingHappens) { Q_ASSERT(!spy.count()); return layer; } Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); KisLayerSP newLayer = qobject_cast(newNode.data()); return newLayer; } void KisImageTest::testFlattenLayer() { FlattenTestImage p; TestUtil::ReferenceImageChecker chk("flatten", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.layer2); //KisLayerSP newLayer = p.image->flattenLayer(p.layer2); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); //KisLayerSP newLayer = p.image->flattenLayer(p.group1); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(400, 100, 200, 100)); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.layer5, true); //KisLayerSP newLayer = p.image->flattenLayer(p.layer5); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 50, 100, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), true); } } -#include +#include template KisLayerSP mergeHelper(ContainerTest &p, KisLayerSP layer) { KisNodeSP parent = layer->parent(); const int newIndex = parent->index(layer) - 1; p.image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); //KisLayerUtils::mergeDown(p.image, layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); KisLayerSP newLayer = qobject_cast(parent->at(newIndex).data()); return newLayer; } void KisImageTest::testMergeDown() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_simple", "imagetest"); { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 213, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_group1_mergedown_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 500, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationInheritsAlpha() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_dst_inheritsalpha", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); // WARN: this check is suspicious! QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_proj_merged_layer2_over_layer5_IA")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50,50, 263, 267)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationCustomCompositeOp() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_dst_customop", "imagetest"); { QCOMPARE(p.layer6->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer6->alphaChannelDisabled(), false); QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer6); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer6_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOpLayerStyle() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_sameop_ls", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(197, 100, 403, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOp() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_sameop_fastpath", "imagetest"); { QCOMPARE(p.layer8->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer8->alphaChannelDisabled(), false); QCOMPARE(p.layer7->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer7->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer8); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(50, 350, 50, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } #include "kis_image_animation_interface.h" void KisImageTest::testMergeDownMultipleFrames() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_simple", "imagetest"); QSet initialFrames; { KisLayerSP l = p.layer5; l->enableAnimation(); KisKeyframeChannel *channel = l->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); channel->addKeyframe(10); channel->addKeyframe(20); channel->addKeyframe(30); QCOMPARE(channel->keyframeCount(), 4); initialFrames = KisLayerUtils::fetchLayerFramesRecursive(l); QCOMPARE(initialFrames.size(), 4); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); QVERIFY(newLayer->isAnimated()); QSet newFrames = KisLayerUtils::fetchLayerFramesRecursive(newLayer); QCOMPARE(newFrames, initialFrames); foreach (int frame, newFrames) { KisImageAnimationInterface *interface = p.image->animationInterface(); int savedSwitchedTime = 0; interface->saveAndResetCurrentTime(frame, &savedSwitchedTime); QCOMPARE(newLayer->exactBounds(), QRect(100,100,100,100)); interface->restoreCurrentTime(&savedSwitchedTime); } p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } template KisNodeSP mergeMultipleHelper(ContainerTest &p, QList selectedNodes, KisNodeSP putAfter) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); p.image->mergeMultipleLayers(selectedNodes, putAfter); //KisLayerUtils::mergeMultipleLayers(p.image, selectedNodes, putAfter); p.image->waitForDone(); Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); return newNode; } void KisImageTest::testMergeMultiple() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergemultiple", "imagetest"); { QList selectedNodes; selectedNodes << p.layer2 << p.group1 << p.layer6; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } p.p.undoStore->undo(); p.image->waitForDone(); // Test reversed order, the result must be the same { QList selectedNodes; selectedNodes << p.layer6 << p.group1 << p.layer2; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } } void testMergeCrossColorSpaceImpl(bool useProjectionColorSpace, bool swapSpaces) { QRect refRect; TestUtil::MaskParent p; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; const KoColorSpace *cs2 = useProjectionColorSpace ? p.image->colorSpace() : KoColorSpaceRegistry::instance()->lab16(); const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16(); if (swapSpaces) { std::swap(cs2, cs3); } dbgKrita << "Testing testMergeCrossColorSpaceImpl:"; dbgKrita << " " << ppVar(cs2); dbgKrita << " " << ppVar(cs3); layer1 = p.layer; layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8, cs2); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8, cs3); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(250, 250, 200, 200); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, layer2->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, layer3->colorSpace())); p.image->addNode(layer2); p.image->addNode(layer3); p.image->initialRefreshGraph(); { KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } { layer2->disableAlphaChannel(true); KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } } void KisImageTest::testMergeCrossColorSpace() { testMergeCrossColorSpaceImpl(true, false); testMergeCrossColorSpaceImpl(true, true); testMergeCrossColorSpaceImpl(false, false); testMergeCrossColorSpaceImpl(false, true); } void KisImageTest::testMergeSelectionMasks() { QRect refRect; TestUtil::MaskParent p; QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(50, 50, 100, 100); KisPaintLayerSP layer1 = p.layer; layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); p.image->initialRefreshGraph(); KisSelectionSP sel = new KisSelection(layer1->paintDevice()->defaultBounds()); sel->pixelSelection()->select(rect2, MAX_SELECTED); KisSelectionMaskSP mask1 = new KisSelectionMask(p.image); mask1->initSelection(sel, layer1); p.image->addNode(mask1, layer1); QVERIFY(!layer1->selection()); mask1->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); sel->pixelSelection()->select(rect3, MAX_SELECTED); KisSelectionMaskSP mask2 = new KisSelectionMask(p.image); mask2->initSelection(sel, layer1); p.image->addNode(mask2, layer1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); mask2->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); QList selectedNodes; selectedNodes << mask2 << mask1; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); QCOMPARE(newLayer->parent(), KisNodeSP(layer1)); QCOMPARE((int)layer1->childCount(), 1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); } } void KisImageTest::testFlattenImage() { FlattenTestImage p; KisImageSP image = p.image; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); { KisLayerUtils::flattenImage(p.image); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } struct FlattenPassThroughTestImage { FlattenPassThroughTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); group4 = new KisGroupLayer(p.image, "group4", OPACITY_OPAQUE_U8); layer5 = new KisPaintLayer(p.image, "paint5", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); QRect rect2(100, 100, 100, 100); QRect rect3(150, 150, 100, 100); QRect rect5(200, 200, 100, 100); QRect rect6(250, 250, 100, 100); group1->setPassThroughMode(true); layer2->paintDevice()->fill(rect2, KoColor(Qt::red, p.image->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::green, p.image->colorSpace())); group4->setPassThroughMode(true); layer5->paintDevice()->fill(rect5, KoColor(Qt::blue, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::yellow, p.image->colorSpace())); p.image->addNode(group1); p.image->addNode(layer2, group1); p.image->addNode(layer3, group1); p.image->addNode(group4); p.image->addNode(layer5, group4); p.image->addNode(layer6, group4); p.image->initialRefreshGraph(); TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisGroupLayerSP group1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; KisGroupLayerSP group4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; }; void KisImageTest::testFlattenPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergeTwoPassThroughLayers() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisGroupLayer")); } } void KisImageTest::testMergePaintOverPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, newLayer); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergePassThroughOverPaintLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } #include "kis_paint_device_debug_utils.h" #include "kis_algebra_2d.h" void KisImageTest::testPaintOverlayMask() { QRect refRect(0, 0, 512, 512); TestUtil::MaskParent p(refRect); QRect fillRect(50, 50, 412, 412); QRect selectionRect(200, 200, 100, 50); KisPaintLayerSP layer1 = p.layer; layer1->paintDevice()->fill(fillRect, KoColor(Qt::yellow, layer1->colorSpace())); KisSelectionMaskSP mask = new KisSelectionMask(p.image); KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(layer1->paintDevice(), p.image)); selection->pixelSelection()->select(selectionRect, 128); selection->pixelSelection()->select(KisAlgebra2D::blowRect(selectionRect,-0.3), 255); mask->setSelection(selection); //mask->setVisible(false); //mask->setActive(false); p.image->addNode(mask, layer1); // a simple layer to disable oblidge child mechanism KisPaintLayerSP layer2 = new KisPaintLayer(p.image, "layer2", OPACITY_OPAQUE_U8); p.image->addNode(layer2); p.image->initialRefreshGraph(); KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "00_initial", "dd"); p.image->setOverlaySelectionMask(mask); p.image->waitForDone(); KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "01_activated", "dd"); p.image->setOverlaySelectionMask(0); p.image->waitForDone(); KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "02_deactivated", "dd"); } QTEST_MAIN(KisImageTest) diff --git a/libs/impex/ExifCheck.h b/libs/impex/ExifCheck.h index caf6d873ba..6f5f3b2fa1 100644 --- a/libs/impex/ExifCheck.h +++ b/libs/impex/ExifCheck.h @@ -1,79 +1,79 @@ /* * 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 ExifCHECK_H #define ExifCHECK_H #include "KisExportCheckRegistry.h" #include #include #include #include -#include -#include -#include +#include +#include +#include class ExifCheck : public KisExportCheckBase { public: ExifCheck(const QString &id, Level level, const QString &customWarning = QString()) : KisExportCheckBase(id, level, customWarning) { if (customWarning.isEmpty()) { m_warning = i18nc("image conversion warning", "The image contains Exif metadata. The metadata will not be saved."); } } bool checkNeeded(KisImageSP image) const override { KisExifInfoVisitor eIV; eIV.visit(image->rootLayer().data()); return eIV.exifInfo(); } Level check(KisImageSP /*image*/) const override { return m_level; } }; class ExifCheckFactory : public KisExportCheckFactory { public: ExifCheckFactory() { } ~ExifCheckFactory() override {} KisExportCheckBase *create(KisExportCheckBase::Level level, const QString &customWarning) override { return new ExifCheck(id(), level, customWarning); } QString id() const override { return "ExifCheck"; } }; #endif // ExifCHECK_H diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp index 3341db586b..b576bf9e56 100644 --- a/libs/libkis/Node.cpp +++ b/libs/libkis/Node.cpp @@ -1,608 +1,615 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program 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 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 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include #include +#include "kis_selection.h" #include "Krita.h" #include "Node.h" #include "Channel.h" #include "Filter.h" #include "Selection.h" #include "GroupLayer.h" #include "CloneLayer.h" #include "FilterLayer.h" #include "FillLayer.h" #include "FileLayer.h" #include "VectorLayer.h" #include "FilterMask.h" #include "SelectionMask.h" #include "LibKisUtils.h" struct Node::Private { Private() {} KisImageWSP image; KisNodeSP node; }; Node::Node(KisImageSP image, KisNodeSP node, QObject *parent) : QObject(parent) , d(new Private) { d->image = image; d->node = node; } Node::~Node() { delete d; } bool Node::operator==(const Node &other) const { return (d->node == other.d->node && d->image == other.d->image); } bool Node::operator!=(const Node &other) const { return !(operator==(other)); } Node *Node::clone() const { KisNodeSP clone = d->node->clone(); Node *node = new Node(0, clone); return node; } bool Node::alphaLocked() const { if (!d->node) return false; KisPaintLayerSP paintLayer = qobject_cast(d->node.data()); if (paintLayer) { return paintLayer->alphaLocked(); } return false; } void Node::setAlphaLocked(bool value) { if (!d->node) return; KisPaintLayerSP paintLayer = qobject_cast(d->node.data()); if (paintLayer) { paintLayer->setAlphaLocked(value); } } QString Node::blendingMode() const { if (!d->node) return QString(); return d->node->compositeOpId(); } void Node::setBlendingMode(QString value) { if (!d->node) return; d->node->setCompositeOpId(value); } QList Node::channels() const { QList channels; if (!d->node) return channels; if (!d->node->inherits("KisLayer")) return channels; Q_FOREACH(KoChannelInfo *info, d->node->colorSpace()->channels()) { Channel *channel = new Channel(d->node, info); channels << channel; } return channels; } QList Node::childNodes() const { QList nodes; if (d->node) { KisNodeList nodeList; int childCount = d->node->childCount(); for (int i = 0; i < childCount; ++i) { nodeList << d->node->at(i); } nodes = LibKisUtils::createNodeList(nodeList, d->image); } return nodes; } bool Node::addChildNode(Node *child, Node *above) { if (!d->node) return false; if (above) { return d->image->addNode(child->node(), d->node, above->node()); } else { return d->image->addNode(child->node(), d->node, d->node->childCount()); } } bool Node::removeChildNode(Node *child) { if (!d->node) return false; return d->image->removeNode(child->node()); } void Node::setChildNodes(QList nodes) { if (!d->node) return; KisNodeSP node = d->node->firstChild(); while (node) { d->image->removeNode(node); node = node->nextSibling(); } Q_FOREACH(Node *node, nodes) { d->image->addNode(node->node(), d->node); } } int Node::colorLabel() const { if (!d->node) return 0; return d->node->colorLabelIndex(); } void Node::setColorLabel(int index) { if (!d->node) return; d->node->setColorLabelIndex(index); } QString Node::colorDepth() const { if (!d->node) return ""; return d->node->colorSpace()->colorDepthId().id(); } QString Node::colorModel() const { if (!d->node) return ""; return d->node->colorSpace()->colorModelId().id(); } QString Node::colorProfile() const { if (!d->node) return ""; return d->node->colorSpace()->profile()->name(); } bool Node::setColorProfile(const QString &colorProfile) { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; KisLayer *layer = qobject_cast(d->node.data()); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile); const KoColorSpace *srcCS = layer->colorSpace(); const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(srcCS->colorModelId().id(), srcCS->colorDepthId().id(), profile); KisChangeProfileVisitor v(srcCS, dstCs); return layer->accept(v); } bool Node::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; KisLayer *layer = qobject_cast(d->node.data()); const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile); const KoColorSpace *srcCS = layer->colorSpace(); const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile); KisColorSpaceConvertVisitor v(d->image, srcCS, dstCs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return layer->accept(v); } bool Node::animated() const { if (!d->node) return false; return d->node->isAnimated(); } void Node::enableAnimation() const { if (!d->node) return; d->node->enableAnimation(); } bool Node::collapsed() const { if (!d->node) return false; return d->node->collapsed(); } void Node::setCollapsed(bool collapsed) { if (!d->node) return; d->node->setCollapsed(collapsed); } bool Node::inheritAlpha() const { if (!d->node) return false; if (!d->node->inherits("KisLayer")) return false; return qobject_cast(d->node)->alphaChannelDisabled(); } void Node::setInheritAlpha(bool value) { if (!d->node) return; if (!d->node->inherits("KisLayer")) return; const_cast(qobject_cast(d->node))->disableAlphaChannel(value); } bool Node::locked() const { if (!d->node) return false; return d->node->userLocked(); } void Node::setLocked(bool value) { if (!d->node) return; d->node->setUserLocked(value); } bool Node::hasExtents() { return !d->node->extent().isEmpty(); } QString Node::name() const { if (!d->node) return QString(); return d->node->name(); } void Node::setName(QString name) { if (!d->node) return; d->node->setName(name); } int Node::opacity() const { if (!d->node) return 0; return d->node->opacity(); } void Node::setOpacity(int value) { if (!d->node) return; if (value < 0) value = 0; if (value > 255) value = 255; d->node->setOpacity(value); } Node* Node::parentNode() const { if (!d->node) return 0; return new Node(d->image, d->node->parent()); } QString Node::type() const { if (!d->node) return QString(); if (qobject_cast(d->node)) { return "paintlayer"; } else if (qobject_cast(d->node)) { return "grouplayer"; } if (qobject_cast(d->node)) { return "filelayer"; } if (qobject_cast(d->node)) { return "filterlayer"; } if (qobject_cast(d->node)) { return "filllayer"; } if (qobject_cast(d->node)) { return "clonelayer"; } if (qobject_cast(d->node)) { return "referenceimageslayer"; } if (qobject_cast(d->node)) { return "vectorlayer"; } if (qobject_cast(d->node)) { return "transparencymask"; } if (qobject_cast(d->node)) { return "filtermask"; } if (qobject_cast(d->node)) { return "transformmask"; } if (qobject_cast(d->node)) { return "selectionmask"; } if (qobject_cast(d->node)) { return "colorizemask"; } return QString(); } QIcon Node::icon() const { QIcon icon; if (d->node) { icon = d->node->icon(); } return icon; } bool Node::visible() const { if (!d->node) return false; return d->node->visible(); } void Node::setVisible(bool visible) { if (!d->node) return; d->node->setVisible(visible); } QByteArray Node::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->node) return ba; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return ba; ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } QByteArray Node::pixelDataAtTime(int x, int y, int w, int h, int time) const { QByteArray ba; if (!d->node || !d->node->isAnimated()) return ba; // KisRasterKeyframeChannel *rkc = dynamic_cast(d->node->getKeyframeChannel(KisKeyframeChannel::Content.id())); if (!rkc) return ba; KisKeyframeSP frame = rkc->keyframeAt(time); if (!frame) return ba; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return ba; rkc->fetchFrame(frame, dev); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } QByteArray Node::projectionPixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->node) return ba; KisPaintDeviceSP dev = d->node->projection(); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } void Node::setPixelData(QByteArray value, int x, int y, int w, int h) { if (!d->node) return; KisPaintDeviceSP dev = d->node->paintDevice(); if (!dev) return; dev->writeBytes((const quint8*)value.constData(), x, y, w, h); } QRect Node::bounds() const { if (!d->node) return QRect(); return d->node->exactBounds(); } void Node::move(int x, int y) { if (!d->node) return; d->node->setX(x); d->node->setY(y); } QPoint Node::position() const { if (!d->node) return QPoint(); return QPoint(d->node->x(), d->node->y()); } bool Node::remove() { if (!d->node) return false; if (!d->node->parent()) return false; return d->image->removeNode(d->node); } Node* Node::duplicate() { if (!d->node) return 0; return new Node(d->image, d->node->clone()); } bool Node::save(const QString &filename, double xRes, double yRes) { if (!d->node) return false; if (filename.isEmpty()) return false; KisPaintDeviceSP projection = d->node->projection(); QRect bounds = d->node->exactBounds(); QString mimeType = KisMimeDatabase::mimeTypeForFile(filename, false); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(doc->createUndoStore(), bounds.right(), bounds.bottom(), projection->compositionSourceColorSpace(), d->node->name()); dst->setResolution(xRes, yRes); doc->setFileBatchMode(Krita::instance()->batchmode()); doc->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", d->node->opacity()); paintLayer->paintDevice()->makeCloneFrom(projection, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->cropImage(bounds); dst->initialRefreshGraph(); bool r = doc->exportDocumentSync(QUrl::fromLocalFile(filename), mimeType.toLatin1()); if (!r) { qWarning() << doc->errorMessage(); } return r; } Node* Node::mergeDown() { if (!d->node) return 0; if (!qobject_cast(d->node.data())) return 0; if (!d->node->prevSibling()) return 0; d->image->mergeDown(qobject_cast(d->node.data()), KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); d->image->waitForDone(); return new Node(d->image, d->node->prevSibling()); } -void Node::scaleNode(int width, int height, QString strategy) +void Node::scaleNode(const QPointF &origin, int width, int height, QString strategy) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy); if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); - d->image->scaleNode(d->node, width, height, actualStrategy); + const QRect bounds(d->node->exactBounds()); + + d->image->scaleNode(d->node, + origin, + qreal(width) / bounds.width(), + qreal(height) / bounds.height(), + actualStrategy, 0); } void Node::rotateNode(double radians) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; - d->image->rotateNode(d->node, radians); + d->image->rotateNode(d->node, radians, 0); } void Node::cropNode(int x, int y, int w, int h) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; QRect rect = QRect(x, y, w, h); d->image->cropNode(d->node, rect); } void Node::shearNode(double angleX, double angleY) { if (!d->node) return; if (!qobject_cast(d->node.data())) return; if (!d->node->parent()) return; - d->image->shearNode(d->node, angleX, angleY); + d->image->shearNode(d->node, angleX, angleY, 0); } QImage Node::thumbnail(int w, int h) { if (!d->node) return QImage(); return d->node->createThumbnail(w, h); } KisPaintDeviceSP Node::paintDevice() const { return d->node->paintDevice(); } KisImageSP Node::image() const { return d->image; } KisNodeSP Node::node() const { return d->node; } diff --git a/libs/libkis/Node.h b/libs/libkis/Node.h index 790ee974c6..11019b73d4 100644 --- a/libs/libkis/Node.h +++ b/libs/libkis/Node.h @@ -1,550 +1,550 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program 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 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 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 LIBKIS_NODE_H #define LIBKIS_NODE_H #include #include #include "kritalibkis_export.h" #include "libkis.h" /** * Node represents a layer or mask in a Krita image's Node hierarchy. Group layers can contain * other layers and masks; layers can contain masks. * */ class KRITALIBKIS_EXPORT Node : public QObject { Q_OBJECT Q_DISABLE_COPY(Node) public: explicit Node(KisImageSP image, KisNodeSP node, QObject *parent = 0); ~Node() override; bool operator==(const Node &other) const; bool operator!=(const Node &other) const; public Q_SLOTS: /** * @brief clone clone the current node. The node is not associated with any image. */ Node *clone() const; /** * @brief alphaLocked checks whether the node is a paint layer and returns whether it is alpha locked * @return whether the paint layer is alpha locked, or false if the node is not a paint layer */ bool alphaLocked() const; /** * @brief setAlphaLocked set the layer to value if the node is paint layer. */ void setAlphaLocked(bool value); /** * @return the blending mode of the layer. The values of the blending modes are defined in @see KoCompositeOpRegistry.h */ QString blendingMode() const; /** * @brief setBlendingMode set the blending mode of the node to the given value * @param value one of the string values from @see KoCompositeOpRegistry.h */ void setBlendingMode(QString value); /** * @brief channels creates a list of Channel objects that can be used individually to * show or hide certain channels, and to retrieve the contents of each channel in a * node separately. * * Only layers have channels, masks do not, and calling channels on a Node that is a mask * will return an empty list. * * @return the list of channels ordered in by position of the channels in pixel position */ QList channels() const; /** * Return a list of child nodes of the current node. The nodes are ordered from the bottommost up. * The function is not recursive. */ QList childNodes() const; /** * @brief addChildNode adds the given node in the list of children. * @param child the node to be added * @param above the node above which this node will be placed * @return false if adding the node failed */ bool addChildNode(Node *child, Node *above); /** * @brief removeChildNode removes the given node from the list of children. * @param child the node to be removed */ bool removeChildNode(Node *child); /** * @brief setChildNodes this replaces the existing set of child nodes with the new set. * @param nodes The list of nodes that will become children, bottom-up -- the first node, * is the bottom-most node in the stack. */ void setChildNodes(QList nodes); /** * colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @return the color depth. */ QString colorDepth() const; /** * @brief colorModel retrieve the current color model of this document: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @return the internal color model string. */ QString colorModel() const; /** * @return the name of the current color profile */ QString colorProfile() const; /** * @brief setColorProfile set the color profile of the image to the given profile. The profile has to * be registered with krita and be compatible with the current color model and depth; the image data * is not converted. * @param colorProfile * @return if assigining the colorprofiel worked */ bool setColorProfile(const QString &colorProfile); /** * @brief setColorSpace convert the node to the given colorspace * @param colorModel A string describing the color model of the node: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @param colorProfile a valid color profile for this color model and color depth combination. */ bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); /** * @brief Krita layers can be animated, i.e., have frames. * @return return true if the layer has frames. Currently, the scripting framework * does not give access to the animation features. */ bool animated() const; /** * @brief enableAnimation make the current layer animated, so it can have frames. */ void enableAnimation() const; /** * Sets the state of the node to the value of @param collapsed */ void setCollapsed(bool collapsed); /** * returns the collapsed state of this node */ bool collapsed() const; /** * Sets a color label index associated to the layer. The actual * color of the label and the number of available colors is * defined by Krita GUI configuration. */ int colorLabel() const; /** * @brief setColorLabel sets a color label index associated to the layer. The actual * color of the label and the number of available colors is * defined by Krita GUI configuration. * @param index an integer corresponding to the set of available color labels. */ void setColorLabel(int index); /** * @brief inheritAlpha checks whether this node has the inherits alpha flag set * @return true if the Inherit Alpha is set */ bool inheritAlpha() const; /** * set the Inherit Alpha flag to the given value */ void setInheritAlpha(bool value); /** * @brief locked checks whether the Node is locked. A locked node cannot be changed. * @return true if the Node is locked, false if it hasn't been locked. */ bool locked() const; /** * set the Locked flag to the give value */ void setLocked(bool value); /** * @brief does the node have any content in it? * @return if node has any content in it */ bool hasExtents(); /** * @return the user-visible name of this node. */ QString name() const; /** * rename the Node to the given name */ void setName(QString name); /** * return the opacity of the Node. The opacity is a value between 0 and 255. */ int opacity() const; /** * set the opacity of the Node to the given value. The opacity is a value between 0 and 255. */ void setOpacity(int value); /** * return the Node that is the parent of the current Node, or 0 if this is the root Node. */ Node* parentNode() const; /** * @brief type Krita has several types of nodes, split in layers and masks. Group * layers can contain other layers, any layer can contain masks. * * @return The type of the node. Valid types are: *
    *
  • paintlayer *
  • grouplayer *
  • filelayer *
  • filterlayer *
  • filllayer *
  • clonelayer *
  • vectorlayer *
  • transparencymask *
  • filtermask *
  • transformmask *
  • selectionmask *
  • colorizemask *
* * If the Node object isn't wrapping a valid Krita layer or mask object, and * empty string is returned. */ virtual QString type() const; /** * @brief icon * @return the icon associated with the layer. */ QIcon icon() const; /** * Check whether the current Node is visible in the layer stack */ bool visible() const; /** * Set the visibility of the current node to @param visible */ void setVisible(bool visible); /** * @brief pixelData reads the given rectangle from the Node's paintable pixels, if those * exist, and returns it as a byte array. The pixel data starts top-left, and is ordered row-first. * * The byte array can be interpreted as follows: 8 bits images have one byte per channel, * and as many bytes as there are channels. 16 bits integer images have two bytes per channel, * representing an unsigned short. 16 bits float images have two bytes per channel, representing * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a * float. * * You can read outside the node boundaries; those pixels will be transparent black. * * The order of channels is: * *
    *
  • Integer RGBA: Blue, Green, Red, Alpha *
  • Float RGBA: Red, Green, Blue, Alpha *
  • GrayA: Gray, Alpha *
  • Selection: selectedness *
  • LabA: L, a, b, Alpha *
  • CMYKA: Cyan, Magenta, Yellow, Key, Alpha *
  • XYZA: X, Y, Z, A *
  • YCbCrA: Y, Cb, Cr, Alpha *
* * The byte array is a copy of the original node data. In Python, you can use bytes, bytearray * and the struct module to interpret the data and construct, for instance, a Pillow Image object. * * If you read the pixeldata of a mask, a filter or generator layer, you get the selection bytes, * which is one channel with values in the range from 0..255. * * If you want to change the pixels of a node you can write the pixels back after manipulation * with setPixelData(). This will only succeed on nodes with writable pixel data, e.g not on groups * or file layers. * * @param x x position from where to start reading * @param y y position from where to start reading * @param w row length to read * @param h number of rows to read * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray pixelData(int x, int y, int w, int h) const; /** * @brief pixelDataAtTime a basic function to get pixeldata from an animated node at a given time. * @param x the position from the left to start reading. * @param y the position from the top to start reader * @param w the row length to read * @param h the number of rows to read * @param time the frame number * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray pixelDataAtTime(int x, int y, int w, int h, int time) const; /** * @brief projectionPixelData reads the given rectangle from the Node's projection (that is, what the node * looks like after all sub-Nodes (like layers in a group or masks on a layer) have been applied, * and returns it as a byte array. The pixel data starts top-left, and is ordered row-first. * * The byte array can be interpreted as follows: 8 bits images have one byte per channel, * and as many bytes as there are channels. 16 bits integer images have two bytes per channel, * representing an unsigned short. 16 bits float images have two bytes per channel, representing * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a * float. * * You can read outside the node boundaries; those pixels will be transparent black. * * The order of channels is: * *
    *
  • Integer RGBA: Blue, Green, Red, Alpha *
  • Float RGBA: Red, Green, Blue, Alpha *
  • GrayA: Gray, Alpha *
  • Selection: selectedness *
  • LabA: L, a, b, Alpha *
  • CMYKA: Cyan, Magenta, Yellow, Key, Alpha *
  • XYZA: X, Y, Z, A *
  • YCbCrA: Y, Cb, Cr, Alpha *
* * The byte array is a copy of the original node data. In Python, you can use bytes, bytearray * and the struct module to interpret the data and construct, for instance, a Pillow Image object. * * If you read the projection of a mask, you get the selection bytes, which is one channel with * values in the range from 0..255. * * If you want to change the pixels of a node you can write the pixels back after manipulation * with setPixelData(). This will only succeed on nodes with writable pixel data, e.g not on groups * or file layers. * * @param x x position from where to start reading * @param y y position from where to start reading * @param w row length to read * @param h number of rows to read * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray projectionPixelData(int x, int y, int w, int h) const; /** * @brief setPixelData writes the given bytes, of which there must be enough, into the * Node, if the Node has writable pixel data: * *
    *
  • paint layer: the layer's original pixels are overwritten *
  • filter layer, generator layer, any mask: the embedded selection's pixels are overwritten. * Note: for these *
* * File layers, Group layers, Clone layers cannot be written to. Calling setPixelData on * those layer types will silently do nothing. * * @param value the byte array representing the pixels. There must be enough bytes available. * Krita will take the raw pointer from the QByteArray and start reading, not stopping before * (number of channels * size of channel * w * h) bytes are read. * * @param x the x position to start writing from * @param y the y position to start writing from * @param w the width of each row * @param h the number of rows to write */ void setPixelData(QByteArray value, int x, int y, int w, int h); /** * @brief bounds return the exact bounds of the node's paint device * @return the bounds, or an empty QRect if the node has no paint device or is empty. */ QRect bounds() const; /** * move the pixels to the given x, y location in the image coordinate space. */ void move(int x, int y); /** * @brief position returns the position of the paint device of this node. The position is * always 0,0 unless the layer has been moved. If you want to know the topleft position of * the rectangle around the actual non-transparent pixels in the node, use bounds(). * @return the top-left position of the node */ QPoint position() const; /** * @brief remove removes this node from its parent image. */ bool remove(); /** * @brief duplicate returns a full copy of the current node. The node is not inserted in the graphc * @return a valid Node object or 0 if the node couldn't be duplicated. */ Node* duplicate(); /** * @brief save exports the given node with this filename. The extension of the filename determines the filetype. * @param filename the filename including extension * @param xRes the horizontal resolution in pixels per pt (there are 72 pts in an inch) * @param yRes the horizontal resolution in pixels per pt (there are 72 pts in an inch) * @return true if saving succeeded, false if it failed. */ bool save(const QString &filename, double xRes, double yRes); /** * @brief mergeDown merges the given node with the first visible node underneath this node in the layerstack. * This will drop all per-layer metadata. */ Node *mergeDown(); /** * @brief scaleNode * @param width * @param height * @param strategy the scaling strategy. There's several ones amongst these that aren't available in the regular UI. *
    *
  • Hermite
  • *
  • Bicubic - Adds pixels using the color of surrounding pixels. Produces smoother tonal gradations than Bilinear.
  • *
  • Box - Replicate pixels in the image. Preserves all the original detail, but can produce jagged effects.
  • *
  • Bilinear - Adds pixels averaging the color values of surrounding pixels. Produces medium quality results when the image is scaled from half to two times the original size.
  • *
  • Bell
  • *
  • BSpline
  • *
  • Lanczos3 - Offers similar results than Bicubic, but maybe a little bit sharper. Can produce light and dark halos along strong edges.
  • *
  • Mitchell
  • *
*/ - void scaleNode(int width, int height, QString strategy); + void scaleNode(const QPointF &origin, int width, int height, QString strategy); /** * @brief rotateNode rotate this layer by the given radians. * @param radians amount the layer should be rotated in, in radians. */ void rotateNode(double radians); /** * @brief cropNode crop this layer. * @param x the left edge of the cropping rectangle. * @param y the top edge of the cropping rectangle * @param w the right edge of the cropping rectangle * @param h the bottom edge of the cropping rectangle */ void cropNode(int x, int y, int w, int h); /** * @brief shearNode perform a shear operation on this node. * @param angleX the X-angle in degrees to shear by * @param angleY the Y-angle in degrees to shear by */ void shearNode(double angleX, double angleY); /** * @brief thumbnail create a thumbnail of the given dimensions. The thumbnail is sized according * to the layer dimensions, not the image dimensions. If the requested size is too big a null * QImage is created. If the current node cannot generate a thumbnail, a transparent QImage of the * requested size is generated. * @return a QImage representing the layer contents. */ QImage thumbnail(int w, int h); private: friend class Filter; friend class Document; friend class Selection; friend class GroupLayer; friend class FileLayer; friend class FilterLayer; friend class FillLayer; friend class VectorLayer; friend class FilterMask; friend class SelectionMask; /** * @brief paintDevice gives access to the internal paint device of this Node * @return the paintdevice or 0 if the node does not have an editable paint device. */ KisPaintDeviceSP paintDevice() const; KisImageSP image() const; KisNodeSP node() const; struct Private; Private *const d; }; #endif // LIBKIS_NODE_H diff --git a/libs/metadata/CMakeLists.txt b/libs/metadata/CMakeLists.txt new file mode 100644 index 0000000000..d33aff38fc --- /dev/null +++ b/libs/metadata/CMakeLists.txt @@ -0,0 +1,32 @@ +set(kritametadata_LIB_SRCS + kis_legacy_importer.cc + kis_meta_data_entry.cc + kis_meta_data_filter.cc + kis_meta_data_filter_p.cc + kis_meta_data_filter_registry.cc + kis_meta_data_filter_registry_model.cc + kis_meta_data_io_backend.cc + kis_meta_data_merge_strategy.cc + kis_meta_data_merge_strategy_p.cc + kis_meta_data_merge_strategy_registry.cc + kis_meta_data_parser.cc + kis_meta_data_schema.cc + kis_meta_data_schema_registry.cc + kis_meta_data_store.cc + kis_meta_data_type_info.cc + kis_meta_data_validator.cc + kis_meta_data_value.cc +) + +add_library(kritametadata SHARED ${kritametadata_LIB_SRCS} ) +generate_export_header(kritametadata) + +target_link_libraries(kritametadata kritaversion kritaglobal kritaplugin kritawidgetutils) + +set_target_properties(kritametadata PROPERTIES + VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} +) + +install(TARGETS kritametadata ${INSTALL_TARGETS_DEFAULT_ARGS}) + +add_subdirectory(tests) diff --git a/libs/image/metadata/kis_legacy_importer.cc b/libs/metadata/kis_legacy_importer.cc similarity index 100% rename from libs/image/metadata/kis_legacy_importer.cc rename to libs/metadata/kis_legacy_importer.cc diff --git a/libs/image/metadata/kis_legacy_importer.h b/libs/metadata/kis_legacy_importer.h similarity index 100% rename from libs/image/metadata/kis_legacy_importer.h rename to libs/metadata/kis_legacy_importer.h diff --git a/libs/image/metadata/kis_meta_data_entry.cc b/libs/metadata/kis_meta_data_entry.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_entry.cc rename to libs/metadata/kis_meta_data_entry.cc diff --git a/libs/image/metadata/kis_meta_data_entry.h b/libs/metadata/kis_meta_data_entry.h similarity index 94% rename from libs/image/metadata/kis_meta_data_entry.h rename to libs/metadata/kis_meta_data_entry.h index 937289c3db..3cc2b87d34 100644 --- a/libs/image/metadata/kis_meta_data_entry.h +++ b/libs/metadata/kis_meta_data_entry.h @@ -1,97 +1,97 @@ /* * 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; 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 _KIS_META_DATA_ENTRY_H_ #define _KIS_META_DATA_ENTRY_H_ -#include +#include #include class QString; namespace KisMetaData { class Value; class Store; class Schema; /** * Represent a metadata entry, a name and a value (\ref KisMetaData::Value). */ -class KRITAIMAGE_EXPORT Entry +class KRITAMETADATA_EXPORT Entry { struct Private; friend class Store; public: /** * Create an invalid entry */ Entry(); /** * Create a new entry. * @param name * @param namespacePrefix * @param value */ Entry(const KisMetaData::Schema* schema, QString name, const KisMetaData::Value& value); Entry(const Entry&); ~Entry(); /** * @return the name of this entry */ QString name() const; /** * @return the namespace of this entry */ const KisMetaData::Schema* schema() const; /** * @return the qualified name of this entry, which is the concatenation of the * namespace and of the name */ QString qualifiedName() const; /** * @return the value of this entry */ const KisMetaData::Value& value() const; /** * @return the value of this entry */ KisMetaData::Value& value(); /** * @return true if this entry is valid */ bool isValid() const; /** * @return true if the name in argument is valid entry name. */ static bool isValidName(const QString& _name); /** * Affect the content of entry to this entry if entry is valid */ Entry& operator=(const Entry& entry); bool operator==(const Entry&) const; private: void setSchema(const KisMetaData::Schema* schema); private: Private* const d; }; } -KRITAIMAGE_EXPORT QDebug operator<<(QDebug debug, const KisMetaData::Entry &c); +KRITAMETADATA_EXPORT QDebug operator<<(QDebug debug, const KisMetaData::Entry &c); #endif diff --git a/libs/image/metadata/kis_meta_data_filter.cc b/libs/metadata/kis_meta_data_filter.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_filter.cc rename to libs/metadata/kis_meta_data_filter.cc diff --git a/libs/image/metadata/kis_meta_data_filter.h b/libs/metadata/kis_meta_data_filter.h similarity index 97% rename from libs/image/metadata/kis_meta_data_filter.h rename to libs/metadata/kis_meta_data_filter.h index dadab01fdf..8efc5c054d 100644 --- a/libs/image/metadata/kis_meta_data_filter.h +++ b/libs/metadata/kis_meta_data_filter.h @@ -1,52 +1,52 @@ /* * Copyright (c) 2007-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 _KIS_META_DATA_FILTER_H_ #define _KIS_META_DATA_FILTER_H_ -#include +#include class QString; namespace KisMetaData { class Store; /** * This class is a base class for filtering a meta data store to alter some * information. For instance, remove author information or change edition * date. */ class Filter { public: virtual ~Filter(); /// @return true if the filter is enabled by default when exporting virtual bool defaultEnabled() const = 0; /// @return the id of this filter virtual QString id() const = 0; /// @return the name of this filter virtual QString name() const = 0; /// @return a description of this filter virtual QString description() const = 0; /** * Apply a filter on a meta data store. */ virtual void filter(KisMetaData::Store*) const = 0; }; } #endif diff --git a/libs/image/metadata/kis_meta_data_filter_p.cc b/libs/metadata/kis_meta_data_filter_p.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_filter_p.cc rename to libs/metadata/kis_meta_data_filter_p.cc diff --git a/libs/image/metadata/kis_meta_data_filter_p.h b/libs/metadata/kis_meta_data_filter_p.h similarity index 100% rename from libs/image/metadata/kis_meta_data_filter_p.h rename to libs/metadata/kis_meta_data_filter_p.h diff --git a/libs/image/metadata/kis_meta_data_filter_registry.cc b/libs/metadata/kis_meta_data_filter_registry.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_filter_registry.cc rename to libs/metadata/kis_meta_data_filter_registry.cc diff --git a/libs/image/metadata/kis_meta_data_filter_registry.h b/libs/metadata/kis_meta_data_filter_registry.h similarity index 90% rename from libs/image/metadata/kis_meta_data_filter_registry.h rename to libs/metadata/kis_meta_data_filter_registry.h index 239440e78d..5ad6c89c7c 100644 --- a/libs/image/metadata/kis_meta_data_filter_registry.h +++ b/libs/metadata/kis_meta_data_filter_registry.h @@ -1,43 +1,43 @@ /* * 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 _KIS_META_DATA_FILTER_REGISTRY_H_ #define _KIS_META_DATA_FILTER_REGISTRY_H_ -#include +#include #include "KoGenericRegistry.h" #include "kis_meta_data_filter.h" namespace KisMetaData { -class KRITAIMAGE_EXPORT FilterRegistry : public KoGenericRegistry +class KRITAMETADATA_EXPORT FilterRegistry : public KoGenericRegistry { public: FilterRegistry(); ~FilterRegistry() override; static FilterRegistry* instance(); private: FilterRegistry(const FilterRegistry&); FilterRegistry& operator=(const FilterRegistry&); }; } #endif diff --git a/libs/image/metadata/kis_meta_data_filter_registry_model.cc b/libs/metadata/kis_meta_data_filter_registry_model.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_filter_registry_model.cc rename to libs/metadata/kis_meta_data_filter_registry_model.cc diff --git a/libs/image/metadata/kis_meta_data_filter_registry_model.h b/libs/metadata/kis_meta_data_filter_registry_model.h similarity index 95% rename from libs/image/metadata/kis_meta_data_filter_registry_model.h rename to libs/metadata/kis_meta_data_filter_registry_model.h index 707c398a10..3a7006338c 100644 --- a/libs/image/metadata/kis_meta_data_filter_registry_model.h +++ b/libs/metadata/kis_meta_data_filter_registry_model.h @@ -1,55 +1,55 @@ /* * 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 _KIS_META_DATA_FILTER_REGISTRY_MODEL_H_ #define _KIS_META_DATA_FILTER_REGISTRY_MODEL_H_ #include "kis_meta_data_filter_registry.h" #include class QStringList; namespace KisMetaData { /** * Use this model to display a list of filters (KisMetaData::Filter) that can be * enabled or disabled. */ -class KRITAIMAGE_EXPORT FilterRegistryModel : public KoGenericRegistryModel +class KRITAMETADATA_EXPORT FilterRegistryModel : public KoGenericRegistryModel { public: FilterRegistryModel(); ~FilterRegistryModel() override; public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex & index) const override; bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override; /// @return a list of filters that are enabled QList enabledFilters() const; /// enable the filters in the given list; others will be disabled. virtual void setEnabledFilters(const QStringList &enabledFilters); private: struct Private; Private* const d; }; } #endif diff --git a/libs/image/metadata/kis_meta_data_io_backend.cc b/libs/metadata/kis_meta_data_io_backend.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_io_backend.cc rename to libs/metadata/kis_meta_data_io_backend.cc diff --git a/libs/image/metadata/kis_meta_data_io_backend.h b/libs/metadata/kis_meta_data_io_backend.h similarity index 95% rename from libs/image/metadata/kis_meta_data_io_backend.h rename to libs/metadata/kis_meta_data_io_backend.h index 115db7bad9..4831aadd6e 100644 --- a/libs/image/metadata/kis_meta_data_io_backend.h +++ b/libs/metadata/kis_meta_data_io_backend.h @@ -1,115 +1,115 @@ /* * 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; 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 _KIS_META_DATA_IO_BACKEND_H_ #define _KIS_META_DATA_IO_BACKEND_H_ -#include +#include #include class QIODevice; namespace KisMetaData { class Store; /** * This is a the interface for input or output backend to KisMetaData. * For instance, to add support to exif or xmp or iptc or dublin core * or anything else, it is needed to extend this interface. */ -class KRITAIMAGE_EXPORT IOBackend +class KRITAMETADATA_EXPORT IOBackend { public: /** * Tell whether the backend input/output from/to binary data * or text (XML or RDF) data. */ enum BackendType { Binary, Text }; enum HeaderType { NoHeader, ///< Don't append any header JpegHeader ///< Append Jpeg-style header }; public: virtual ~IOBackend() {}; virtual QString id() const = 0; virtual QString name() const = 0; /** * @return the type of the backend */ virtual BackendType type() const = 0; /** * @return tell if this backend support saving */ virtual bool supportSaving() const = 0; /** * @param store the list of metadata to save * @param ioDevice the device to where the metadata will be saved * @param headerType determine if an header must be prepend to the binary header, and if it does, * which type of header * @return true if the save was successful (XXX: actually, all backends always return true...) */ virtual bool saveTo(Store* store, QIODevice* ioDevice, HeaderType headerType = NoHeader) const = 0; /** * @param store the list of metadata * @return true if this backend is capable of saving all the metadata * of the store */ virtual bool canSaveAllEntries(Store* store) const = 0; /** * @return true if this backend support loading */ virtual bool supportLoading() const = 0; /** * @param store the list of metadata to load * @param ioDevice the device from where the metadata will be loaded * @return true if the load was successful */ virtual bool loadFrom(Store* store, QIODevice* ioDevice) const = 0; }; -class KRITAIMAGE_EXPORT IOBackendRegistry : public KoGenericRegistry +class KRITAMETADATA_EXPORT IOBackendRegistry : public KoGenericRegistry { public: IOBackendRegistry(); ~IOBackendRegistry() override; static IOBackendRegistry* instance(); }; } #endif diff --git a/libs/image/metadata/kis_meta_data_merge_strategy.cc b/libs/metadata/kis_meta_data_merge_strategy.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_merge_strategy.cc rename to libs/metadata/kis_meta_data_merge_strategy.cc diff --git a/libs/image/metadata/kis_meta_data_merge_strategy.h b/libs/metadata/kis_meta_data_merge_strategy.h similarity index 96% rename from libs/image/metadata/kis_meta_data_merge_strategy.h rename to libs/metadata/kis_meta_data_merge_strategy.h index 61b735493e..0d7b97c20b 100644 --- a/libs/image/metadata/kis_meta_data_merge_strategy.h +++ b/libs/metadata/kis_meta_data_merge_strategy.h @@ -1,62 +1,62 @@ /* * 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 _KIS_META_DATA_MERGE_STRATEGY_H_ #define _KIS_META_DATA_MERGE_STRATEGY_H_ #include -#include +#include class QString; namespace KisMetaData { class Store; /** * This is an interface which serves as a base class for meta data store merge * strategy. * This is used to decide which entries of a metadata store is kept, or how they * are modified when a list of meta data stores are merged together. */ -class KRITAIMAGE_EXPORT MergeStrategy +class KRITAMETADATA_EXPORT MergeStrategy { public: virtual ~MergeStrategy(); /// @return the id of this merge strategy virtual QString id() const = 0; /// @return the name of this merge strategy virtual QString name() const = 0; /// @return a description of this merge strategy virtual QString description() const = 0; /** * Call this function to merge a list of meta data stores in one. * @param dst the destination store * @param srcs the list of source meta data store * @param scores a list of score which defines the importance of each store compared to the other * the sum of score is expected to be equal to 1.0. * One way to attribute a score is to compute the area of each layer and then * to give a higher score to the biggest layer. * srcs and scores list must have the same size. */ virtual void merge(Store* dst, QList srcs, QList scores) const = 0; }; } #endif diff --git a/libs/image/metadata/kis_meta_data_merge_strategy_p.cc b/libs/metadata/kis_meta_data_merge_strategy_p.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_merge_strategy_p.cc rename to libs/metadata/kis_meta_data_merge_strategy_p.cc diff --git a/libs/image/metadata/kis_meta_data_merge_strategy_p.h b/libs/metadata/kis_meta_data_merge_strategy_p.h similarity index 100% rename from libs/image/metadata/kis_meta_data_merge_strategy_p.h rename to libs/metadata/kis_meta_data_merge_strategy_p.h diff --git a/libs/image/metadata/kis_meta_data_merge_strategy_registry.cc b/libs/metadata/kis_meta_data_merge_strategy_registry.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_merge_strategy_registry.cc rename to libs/metadata/kis_meta_data_merge_strategy_registry.cc diff --git a/libs/image/metadata/kis_meta_data_merge_strategy_registry.h b/libs/metadata/kis_meta_data_merge_strategy_registry.h similarity index 89% rename from libs/image/metadata/kis_meta_data_merge_strategy_registry.h rename to libs/metadata/kis_meta_data_merge_strategy_registry.h index 9f30a2f32f..3592d546be 100644 --- a/libs/image/metadata/kis_meta_data_merge_strategy_registry.h +++ b/libs/metadata/kis_meta_data_merge_strategy_registry.h @@ -1,44 +1,44 @@ /* * 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 _KIS_META_DATA_MERGE_STRATEGY_REGISTRY_H_ #define _KIS_META_DATA_MERGE_STRATEGY_REGISTRY_H_ -#include +#include #include "KoGenericRegistry.h" #include "kis_meta_data_merge_strategy.h" namespace KisMetaData { -class KRITAIMAGE_EXPORT MergeStrategyRegistry : public KoGenericRegistry +class KRITAMETADATA_EXPORT MergeStrategyRegistry : public KoGenericRegistry { public: MergeStrategyRegistry(); ~MergeStrategyRegistry() override; static MergeStrategyRegistry* instance(); private: MergeStrategyRegistry(const MergeStrategyRegistry&); MergeStrategyRegistry& operator=(const MergeStrategyRegistry&); }; } #endif diff --git a/libs/image/metadata/kis_meta_data_parser.cc b/libs/metadata/kis_meta_data_parser.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_parser.cc rename to libs/metadata/kis_meta_data_parser.cc diff --git a/libs/image/metadata/kis_meta_data_parser.h b/libs/metadata/kis_meta_data_parser.h similarity index 94% rename from libs/image/metadata/kis_meta_data_parser.h rename to libs/metadata/kis_meta_data_parser.h index 591a91ed85..6e45f58663 100644 --- a/libs/image/metadata/kis_meta_data_parser.h +++ b/libs/metadata/kis_meta_data_parser.h @@ -1,42 +1,42 @@ /* * Copyright (c) 2009 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 _KIS_META_DATA_PARSER_H_ #define _KIS_META_DATA_PARSER_H_ -#include +#include #include namespace KisMetaData { class TypeInfo; class Value; /** * This class allow to parse from a string and return a value. */ -class KRITAIMAGE_EXPORT Parser +class KRITAMETADATA_EXPORT Parser { friend class TypeInfo; public: virtual ~Parser(); virtual Value parse(const QString&) const = 0; }; } #endif diff --git a/libs/image/metadata/kis_meta_data_parser_p.h b/libs/metadata/kis_meta_data_parser_p.h similarity index 100% rename from libs/image/metadata/kis_meta_data_parser_p.h rename to libs/metadata/kis_meta_data_parser_p.h diff --git a/libs/image/metadata/kis_meta_data_schema.cc b/libs/metadata/kis_meta_data_schema.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_schema.cc rename to libs/metadata/kis_meta_data_schema.cc diff --git a/libs/image/metadata/kis_meta_data_schema.h b/libs/metadata/kis_meta_data_schema.h similarity index 93% rename from libs/image/metadata/kis_meta_data_schema.h rename to libs/metadata/kis_meta_data_schema.h index cd1f46508f..b8fe6ab1ed 100644 --- a/libs/image/metadata/kis_meta_data_schema.h +++ b/libs/metadata/kis_meta_data_schema.h @@ -1,75 +1,75 @@ /* * 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; 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 _KIS_META_DATA_SCHEMA_H_ #define _KIS_META_DATA_SCHEMA_H_ -#include +#include #include class QString; namespace KisMetaData { class SchemaRegistry; class TypeInfo; -class KRITAIMAGE_EXPORT Schema +class KRITAMETADATA_EXPORT Schema { friend class SchemaRegistry; public: virtual ~Schema(); static const QString TIFFSchemaUri; static const QString EXIFSchemaUri; static const QString DublinCoreSchemaUri; static const QString XMPSchemaUri; static const QString XMPRightsSchemaUri; static const QString XMPMediaManagementUri; static const QString MakerNoteSchemaUri; static const QString IPTCSchemaUri; static const QString PhotoshopSchemaUri; private: Schema(); Schema(const QString & _uri, const QString & _ns); public: /** * @return the \ref TypeInfo associated with a given a property ( @p _propertyName ). */ const TypeInfo* propertyType(const QString& _propertyName) const; /** * @return the \ref TypeInfo describing a given structure of that scheam */ const TypeInfo* structure(const QString& _structureName) const; public: QString uri() const; QString prefix() const; QString generateQualifiedName(const QString &) const; private: struct Private; Private* const d; }; } -KRITAIMAGE_EXPORT QDebug operator<<(QDebug debug, const KisMetaData::Schema &c); +KRITAMETADATA_EXPORT QDebug operator<<(QDebug debug, const KisMetaData::Schema &c); #endif diff --git a/libs/image/metadata/kis_meta_data_schema_p.h b/libs/metadata/kis_meta_data_schema_p.h similarity index 100% rename from libs/image/metadata/kis_meta_data_schema_p.h rename to libs/metadata/kis_meta_data_schema_p.h diff --git a/libs/image/metadata/kis_meta_data_schema_registry.cc b/libs/metadata/kis_meta_data_schema_registry.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_schema_registry.cc rename to libs/metadata/kis_meta_data_schema_registry.cc diff --git a/libs/image/metadata/kis_meta_data_schema_registry.h b/libs/metadata/kis_meta_data_schema_registry.h similarity index 92% rename from libs/image/metadata/kis_meta_data_schema_registry.h rename to libs/metadata/kis_meta_data_schema_registry.h index 42dd7f7216..904b7278eb 100644 --- a/libs/image/metadata/kis_meta_data_schema_registry.h +++ b/libs/metadata/kis_meta_data_schema_registry.h @@ -1,69 +1,69 @@ /* * 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; 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 _KIS_META_DATA_SCHEMA_REGISTRY_H_ #define _KIS_META_DATA_SCHEMA_REGISTRY_H_ -#include +#include class QString; class QDebug; namespace KisMetaData { class Schema; -class KRITAIMAGE_EXPORT SchemaRegistry +class KRITAMETADATA_EXPORT SchemaRegistry { struct Private; SchemaRegistry(); ~SchemaRegistry(); public: /** * Creates a new schema. * @param uri the name of the schema * @param prefix the namespace prefix used for this schema * @return the schema associated with the uri (it can return 0, if no schema exist * for the uri, but the prefix was already used, and it can be an already existing * schema if the uri was already included) */ const KisMetaData::Schema* create(const QString & uri, const QString & prefix); /** * @return the schema for this uri */ const Schema* schemaFromUri(const QString & uri) const; /** * @return the schema for this prefix */ const Schema* schemaFromPrefix(const QString & prefix) const; /** * Return an instance of the SchemaRegistry. * Creates an instance if that has never happened before and returns * the singleton instance. * Initialize it with default schemas. */ static KisMetaData::SchemaRegistry* instance(); private: Private* const d; }; } -KRITAIMAGE_EXPORT QDebug operator<<(QDebug debug, const KisMetaData::Schema &c); +KRITAMETADATA_EXPORT QDebug operator<<(QDebug debug, const KisMetaData::Schema &c); #endif diff --git a/libs/image/metadata/kis_meta_data_store.cc b/libs/metadata/kis_meta_data_store.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_store.cc rename to libs/metadata/kis_meta_data_store.cc diff --git a/libs/image/metadata/kis_meta_data_store.h b/libs/metadata/kis_meta_data_store.h similarity index 98% rename from libs/image/metadata/kis_meta_data_store.h rename to libs/metadata/kis_meta_data_store.h index 2275d61c7f..57e9dc1442 100644 --- a/libs/image/metadata/kis_meta_data_store.h +++ b/libs/metadata/kis_meta_data_store.h @@ -1,182 +1,182 @@ /* * 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; 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 _KIS_META_DATA_STORE_H_ #define _KIS_META_DATA_STORE_H_ -#include +#include #include namespace KisMetaData { class Schema; class Entry; class Filter; class Value; /** * This class holds the list of metadata entries and schemas (for instance the * author of the image, copyright holder, license, aperture, speed...) */ -class KRITAIMAGE_EXPORT Store +class KRITAMETADATA_EXPORT Store { struct Private; public: Store(); Store(const Store&); ~Store(); public: /** * Copy the entries from store inside this store */ void copyFrom(const Store* store); /** * @return true if there is no metadata in this store. */ bool empty() const; bool isEmpty() const; /** * Insert a new entry. * * @param entry the new entry to insert in the metadata store, it * must be a key which doesn't already exist * * @return false if the entry couldn't be included whether because the key already * exists */ bool addEntry(const Entry& entry); /** * Give access to a metadata entry * @param entryKey the entryKey as the qualified name of the entry */ Entry& getEntry(const QString & entryKey); /** * Give access to a metadata entry * @param uri the uri of the schema * @param entryName the name of the entry */ Entry& getEntry(const QString & uri, const QString & entryName); /** * Give access to a metadata entry * @param schema the schema * @param entryName the name of the entry */ Entry& getEntry(const KisMetaData::Schema* schema, const QString & entryName); /** * Give access to a metadata entry * @param entryKey the entryKey as the qualified name of the entry */ const Entry& getEntry(const QString & entryKey) const; /** * Give access to a metadata entry * @param uri the uri of the schema * @param entryName the name of the entry */ const Entry& getEntry(const QString & uri, const QString & entryName) const; /** * Give access to a metadata entry * @param schema the schema * @param entryName the name of the entry */ const Entry& getEntry(const KisMetaData::Schema* schema, const QString & entryName) const; /** * Remove an entry. * @param entryKey the entryKey as the qualified name of the entry */ void removeEntry(const QString & entryKey); /** * Remove an entry. * @param uri the uri of the schema * @param entryName the name of the entry */ void removeEntry(const QString & uri, const QString & entryName); /** * Remove an entry. * @param schema the schema * @param entryName the name of the entry */ void removeEntry(const KisMetaData::Schema* schema, const QString & entryName); /** * Return the value associated with this entry name and uri. * @param uri * @param entryName * @return the value */ const Value& getValue(const QString & uri, const QString & entryName) const; QHash::const_iterator begin() const; QHash::const_iterator end() const; /** * @param entryKey the entryKey as the qualified name of the entry * @return true if an entry with the given key exist in the store */ bool containsEntry(const QString & entryKey) const; /** * @return true if the store contains this entry */ bool containsEntry(const KisMetaData::Schema* schema, const QString & entryName) const; /** * @param uri * @param entryName * @return true if an entry with the given uri and entry name exist in the store */ bool containsEntry(const QString & uri, const QString & entryName) const; /** * Dump on kdDebug the metadata store. */ void debugDump() const; /** * Apply a list of filters on a store */ void applyFilters(const QList & filters); /** * @return the list of keys */ QList keys() const; /** * @return the list of entries */ QList entries() const; private: Private* const d; }; } #endif diff --git a/libs/image/metadata/kis_meta_data_type_info.cc b/libs/metadata/kis_meta_data_type_info.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_type_info.cc rename to libs/metadata/kis_meta_data_type_info.cc diff --git a/libs/image/metadata/kis_meta_data_type_info.h b/libs/metadata/kis_meta_data_type_info.h similarity index 96% rename from libs/image/metadata/kis_meta_data_type_info.h rename to libs/metadata/kis_meta_data_type_info.h index 8ee20ed4c7..63d05cc011 100644 --- a/libs/image/metadata/kis_meta_data_type_info.h +++ b/libs/metadata/kis_meta_data_type_info.h @@ -1,101 +1,101 @@ /* * Copyright (c) 2009 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 _KIS_META_DATA_TYPE_INFO_H_ #define _KIS_META_DATA_TYPE_INFO_H_ #include #include -#include +#include namespace KisMetaData { class Parser; class Schema; class Value; -class KRITAIMAGE_EXPORT TypeInfo +class KRITAMETADATA_EXPORT TypeInfo { public: enum PropertyType { BooleanType, IntegerType, DateType, TextType, OrderedArrayType, UnorderedArrayType, AlternativeArrayType, LangArrayType, StructureType, RationalType, GPSCoordinateType, OpenedChoice, ClosedChoice }; - class KRITAIMAGE_EXPORT Choice + class KRITAMETADATA_EXPORT Choice { public: Choice(const Value&, const QString& hint); Choice(const Choice&); Choice& operator=(const Choice&); ~Choice(); public: const Value& value() const; const QString& hint() const; private: struct Private; Private* const d; }; private: TypeInfo(PropertyType _propertiesType); /** * Create a \ref TypeInfo for a */ TypeInfo(PropertyType _propertiesType, const TypeInfo* _embedded); /** * Create a \ref TypeInfo for a choice (either open or closed). * @param _propertiesType either OpenedChoice or ClosedChoice */ TypeInfo(PropertyType _propertiesType, const TypeInfo* _embedded, const QList< Choice >&); /** * Create a \ref TypeInfo for a structure. */ TypeInfo(Schema* _structureSchema, const QString& name); ~TypeInfo(); public: PropertyType propertyType() const; const TypeInfo* embeddedPropertyType() const; const QList< Choice >& choices() const; Schema* structureSchema() const; const QString& structureName() const; const Parser* parser() const; /** * @return true if @p value has a type that is correct for this \ref TypeInfo */ bool hasCorrectType(const Value& value) const; /** * @return true if @p value has a value acceptable for this \ref TypeInfo */ bool hasCorrectValue(const Value& value) const; public: struct Private; private: Private* const d; }; } #endif diff --git a/libs/image/metadata/kis_meta_data_type_info_p.h b/libs/metadata/kis_meta_data_type_info_p.h similarity index 97% rename from libs/image/metadata/kis_meta_data_type_info_p.h rename to libs/metadata/kis_meta_data_type_info_p.h index e367e0e9a8..ed02b19ab6 100644 --- a/libs/image/metadata/kis_meta_data_type_info_p.h +++ b/libs/metadata/kis_meta_data_type_info_p.h @@ -1,48 +1,48 @@ /* * Copyright (c) 2009 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 "kis_meta_data_type_info.h" #include -struct KRITAIMAGE_EXPORT KisMetaData::TypeInfo::Private { +struct KRITAMETADATA_EXPORT KisMetaData::TypeInfo::Private { Private() : embeddedTypeInfo(0), structureSchema(0), parser(0) {} PropertyType propertyType; const TypeInfo* embeddedTypeInfo; QList< Choice> choices; Schema* structureSchema; QString structureName; const Parser* parser; private: static QHash< const TypeInfo*, const TypeInfo*> orderedArrays; static QHash< const TypeInfo*, const TypeInfo*> unorderedArrays; static QHash< const TypeInfo*, const TypeInfo*> alternativeArrays; public: static const TypeInfo* Boolean; static const TypeInfo* Integer; static const TypeInfo* Date; static const TypeInfo* Text; static const TypeInfo* Rational; static const TypeInfo* GPSCoordinate; static const TypeInfo* orderedArray(const TypeInfo*); static const TypeInfo* unorderedArray(const TypeInfo*); static const TypeInfo* alternativeArray(const TypeInfo*); static const TypeInfo* createChoice(PropertyType _propertiesType, const TypeInfo* _embedded, const QList< Choice >&); static const TypeInfo* createStructure(Schema* _structureSchema, const QString& name); static const TypeInfo* LangArray; }; diff --git a/libs/image/metadata/kis_meta_data_validator.cc b/libs/metadata/kis_meta_data_validator.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_validator.cc rename to libs/metadata/kis_meta_data_validator.cc diff --git a/libs/image/metadata/kis_meta_data_validator.h b/libs/metadata/kis_meta_data_validator.h similarity index 94% rename from libs/image/metadata/kis_meta_data_validator.h rename to libs/metadata/kis_meta_data_validator.h index c7a49c49d7..273b15e8b7 100644 --- a/libs/image/metadata/kis_meta_data_validator.h +++ b/libs/metadata/kis_meta_data_validator.h @@ -1,77 +1,77 @@ /* * Copyright (c) 2009 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 _KIS_META_DATA_VALIDATION_RESULT_H_ #define _KIS_META_DATA_VALIDATION_RESULT_H_ #include #include -#include +#include namespace KisMetaData { class Store; /** * This class contains information on the validation results of a \ref KisMetaData::Store . */ -class KRITAIMAGE_EXPORT Validator +class KRITAMETADATA_EXPORT Validator { public: - class KRITAIMAGE_EXPORT Reason + class KRITAMETADATA_EXPORT Reason { friend class Validator; friend class QMap; public: enum Type { UNKNOWN_REASON, UNKNOWN_ENTRY, INVALID_TYPE, INVALID_VALUE }; public: Reason(Type type = UNKNOWN_REASON); Reason(const Reason&); Reason& operator=(const Reason&); public: ~Reason(); Type type() const; private: struct Private; Private* const d; }; public: /** * Validate a store. This constructore will call the \ref revalidate function. */ Validator(const Store*); ~Validator(); int countInvalidEntries() const; int countValidEntries() const; const QMap& invalidEntries() const; /** * Call this function to revalidate the store. */ void revalidate(); private: struct Private; Private* const d; }; } #endif diff --git a/libs/image/metadata/kis_meta_data_value.cc b/libs/metadata/kis_meta_data_value.cc similarity index 100% rename from libs/image/metadata/kis_meta_data_value.cc rename to libs/metadata/kis_meta_data_value.cc diff --git a/libs/image/metadata/kis_meta_data_value.h b/libs/metadata/kis_meta_data_value.h similarity index 96% rename from libs/image/metadata/kis_meta_data_value.h rename to libs/metadata/kis_meta_data_value.h index 7c6f158379..0fd2fbd663 100644 --- a/libs/image/metadata/kis_meta_data_value.h +++ b/libs/metadata/kis_meta_data_value.h @@ -1,134 +1,134 @@ /* * 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; 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 _KIS_META_DATA_VALUE_H_ #define _KIS_META_DATA_VALUE_H_ #include #include -#include +#include #include class QVariant; namespace KisMetaData { struct Rational : public boost::equality_comparable { explicit Rational(qint32 n = 0, qint32 d = 1) : numerator(n), denominator(d) {} qint32 numerator; qint32 denominator; bool operator==(const Rational& ur) const { return numerator == ur.numerator && denominator == ur.denominator; } }; /** * Value is build on top of QVariant to extend it to support the various types * and extensions through property qualifiers. */ -class KRITAIMAGE_EXPORT Value +class KRITAMETADATA_EXPORT Value { struct Private; public: /// Define the possible value type enum ValueType { Invalid, Variant, OrderedArray, UnorderedArray, AlternativeArray, LangArray, Structure, Rational }; public: Value(); Value(const QVariant& value); /** * @param type is one of OrderedArray, UnorderedArray, AlternativeArray * or LangArray */ Value(const QList& array, ValueType type = OrderedArray); Value(const QMap& structure); Value(const KisMetaData::Rational& rational); Value(const Value& v); Value& operator=(const Value& v); ~Value(); public: void addPropertyQualifier(const QString& _name, const Value&); const QMap& propertyQualifiers() const; public: /// @return the type of this Value ValueType type() const; /** * @return the value as a double, or null if it's not possible, rationals are evaluated */ double asDouble() const; /** * @return the value as an integer, or null if it's not possible, rationals are evaluated */ int asInteger() const; /** * @return the Variant hold by this Value, or an empty QVariant if this Value is not a Variant */ QVariant asVariant() const; /** * Set this Value to the given variant, or does nothing if this Value is not a Variant. * @return true if the value was changed */ bool setVariant(const QVariant& variant); bool setStructureVariant(const QString& fieldNAme, const QVariant& variant); bool setArrayVariant(int index, const QVariant& variant); /** * @return the Rational hold by this Value, or a null rational if this Value is not * an Rational */ KisMetaData::Rational asRational() const; /** * @return the array hold by this Value, or an empty array if this Value is not either * an OrderedArray, UnorderedArray or AlternativeArray */ QList asArray() const; /** * @return true if this Value is either an OrderedArray, UnorderedArray or AlternativeArray */ bool isArray() const; /** * @return the structure hold by this Value, or an empty structure if this Value is not a Structure */ QMap asStructure() const; /** * It's a convenient function that build a map from a LangArray using the property * qualifier "xml:lang" for the key of the map. */ QMap asLangArray() const; QString toString() const; public: bool operator==(const Value&) const; Value& operator+=(const Value&); private: Private* const d; }; } -KRITAIMAGE_EXPORT QDebug operator<<(QDebug debug, const KisMetaData::Value &v); +KRITAMETADATA_EXPORT QDebug operator<<(QDebug debug, const KisMetaData::Value &v); #endif diff --git a/libs/metadata/tests/CMakeLists.txt b/libs/metadata/tests/CMakeLists.txt new file mode 100644 index 0000000000..53da21a131 --- /dev/null +++ b/libs/metadata/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) + +include(ECMAddTests) +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_tests( + kis_meta_data_test.cpp + NAME_PREFIX "libs-metadata-" + LINK_LIBRARIES kritametadata Qt5::Test) diff --git a/libs/image/tests/kis_meta_data_test.cpp b/libs/metadata/tests/kis_meta_data_test.cpp similarity index 100% rename from libs/image/tests/kis_meta_data_test.cpp rename to libs/metadata/tests/kis_meta_data_test.cpp diff --git a/libs/image/tests/kis_meta_data_test.h b/libs/metadata/tests/kis_meta_data_test.h similarity index 100% rename from libs/image/tests/kis_meta_data_test.h rename to libs/metadata/tests/kis_meta_data_test.h diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 8fc647779c..e5359e84fe 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,580 +1,584 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ${EXIV2_INCLUDE_DIR} ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ) add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) find_library(APPKIT_LIBRARY AppKit) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp canvas/kis_paintop_transformation_connector.cpp canvas/kis_display_color_converter.cpp canvas/kis_display_filter.cpp canvas/kis_exposure_gamma_correction_interface.cpp canvas/kis_tool_proxy.cpp canvas/kis_canvas_decoration.cc canvas/kis_coordinates_converter.cpp canvas/kis_grid_manager.cpp canvas/kis_grid_decoration.cpp canvas/kis_grid_config.cpp canvas/kis_prescaled_projection.cpp canvas/kis_qpainter_canvas.cpp canvas/kis_projection_backend.cpp canvas/kis_update_info.cpp canvas/kis_image_patch.cpp canvas/kis_image_pyramid.cpp canvas/kis_infinity_manager.cpp canvas/kis_change_guides_command.cpp canvas/kis_guides_decoration.cpp canvas/kis_guides_manager.cpp canvas/kis_guides_config.cpp canvas/kis_snap_config.cpp canvas/kis_snap_line_strategy.cpp canvas/KisSnapPointStrategy.cpp dialogs/kis_about_application.cpp dialogs/kis_dlg_adj_layer_props.cc dialogs/kis_dlg_adjustment_layer.cc dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_generator_layer.cpp dialogs/kis_dlg_file_layer.cpp dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_stroke_selection_properties.cpp dialogs/kis_dlg_image_properties.cc dialogs/kis_dlg_layer_properties.cc dialogs/kis_dlg_preferences.cc dialogs/slider_and_spin_box_sync.cpp dialogs/kis_dlg_blacklist_cleanup.cpp dialogs/kis_dlg_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp dialogs/KisSessionManagerDialog.cpp dialogs/KisNewWindowLayoutDialog.cpp flake/kis_node_dummies_graph.cpp flake/kis_dummies_facade_base.cpp flake/kis_dummies_facade.cpp flake/kis_node_shapes_graph.cpp flake/kis_node_shape.cpp flake/kis_shape_controller.cpp flake/kis_shape_layer.cc flake/kis_shape_layer_canvas.cpp flake/kis_shape_selection.cpp flake/kis_shape_selection_canvas.cpp flake/kis_shape_selection_model.cpp flake/kis_take_all_shapes_command.cpp brushhud/kis_uniform_paintop_property_widget.cpp brushhud/kis_brush_hud.cpp brushhud/kis_round_hud_button.cpp brushhud/kis_dlg_brush_hud_config.cpp brushhud/kis_brush_hud_properties_list.cpp brushhud/kis_brush_hud_properties_config.cpp kis_aspect_ratio_locker.cpp kis_autogradient.cc kis_bookmarked_configurations_editor.cc kis_bookmarked_configurations_model.cc kis_bookmarked_filter_configurations_model.cc KisPaintopPropertiesBase.cpp kis_canvas_resource_provider.cpp kis_derived_resources.cpp kis_categories_mapper.cpp kis_categorized_list_model.cpp kis_categorized_item_delegate.cpp kis_clipboard.cc kis_config.cc kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_cursor.cc kis_cursor_cache.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_change_file_layer_command.h kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc kis_histogram_view.cc KisImageBarrierLockerWithFeedback.cpp kis_image_manager.cc kis_image_view_converter.cpp kis_import_catcher.cc kis_layer_manager.cc kis_mask_manager.cc kis_mimedata.cpp kis_node_commands_adapter.cpp kis_node_manager.cpp kis_node_juggler_compressed.cpp kis_node_selection_adapter.cpp kis_node_insertion_adapter.cpp KisNodeDisplayModeAdapter.cpp kis_node_model.cpp kis_node_filter_proxy_model.cpp kis_model_index_converter_base.cpp kis_model_index_converter.cpp kis_model_index_converter_show_all.cpp kis_painting_assistant.cc kis_painting_assistants_decoration.cpp KisDecorationsManager.cpp kis_paintop_box.cc kis_paintop_option.cpp kis_paintop_options_model.cpp kis_paintop_settings_widget.cpp kis_popup_palette.cpp kis_png_converter.cpp kis_preference_set_registry.cpp KisResourceServerProvider.cpp KisResourceBundleServerProvider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc KisSelectionActionsAdapter.cpp kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp KisActionPlugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp KisWelcomePageWidget.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp opengl/kis_opengl_shader_loader.cpp opengl/kis_texture_tile_info_pool.cpp opengl/KisOpenGLUpdateInfoBuilder.cpp kis_fps_decoration.cpp + tool/KisToolChangesTracker.cpp + tool/KisToolChangesTrackerData.cpp tool/kis_selection_tool_helper.cpp tool/kis_selection_tool_config_widget_helper.cpp tool/kis_rectangle_constraint_widget.cpp tool/kis_shape_tool_helper.cpp tool/kis_tool.cc tool/kis_delegated_tool_policies.cpp tool/kis_tool_freehand.cc tool/kis_speed_smoother.cpp tool/kis_painting_information_builder.cpp tool/kis_stabilized_events_sampler.cpp tool/kis_tool_freehand_helper.cpp tool/kis_tool_multihand_helper.cpp tool/kis_figure_painting_tool_helper.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/KisStabilizerDelayedPaintHelper.cpp tool/KisStrokeSpeedMonitor.cpp tool/strokes/freehand_stroke.cpp tool/strokes/KisStrokeEfficiencyMeasurer.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp tool/strokes/KisFreehandStrokeInfo.cpp tool/strokes/KisMaskedFreehandStrokePainter.cpp tool/strokes/KisMaskingBrushRenderer.cpp tool/strokes/KisMaskingBrushCompositeOpFactory.cpp tool/strokes/move_stroke_strategy.cpp widgets/kis_cmb_composite.cc widgets/kis_cmb_contour.cpp widgets/kis_cmb_gradient.cpp widgets/kis_paintop_list_widget.cpp widgets/kis_cmb_idlist.cc widgets/kis_color_space_selector.cc widgets/kis_advanced_color_space_selector.cc widgets/kis_cie_tongue_widget.cpp widgets/kis_tone_curve_widget.cpp widgets/kis_curve_widget.cpp widgets/kis_custom_image_widget.cc widgets/kis_image_from_clipboard_widget.cpp widgets/kis_double_widget.cc widgets/kis_filter_selector_widget.cc widgets/kis_gradient_chooser.cc widgets/kis_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_paintop_presets_save.cpp widgets/kis_paintop_preset_icon_library.cpp widgets/kis_pattern_chooser.cc widgets/kis_preset_chooser.cpp widgets/kis_progress_widget.cpp widgets/kis_selection_options.cc widgets/kis_scratch_pad.cpp widgets/kis_scratch_pad_event_filter.cpp widgets/kis_preset_selector_strip.cpp widgets/kis_slider_spin_box.cpp widgets/KisSelectionPropertySlider.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/kis_categorized_list_view.cpp widgets/kis_widget_chooser.cpp widgets/kis_tool_button.cpp widgets/kis_floating_message.cpp widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp widgets/kis_elided_label.cpp widgets/kis_stopgradient_slider_widget.cpp widgets/kis_preset_live_preview_view.cpp widgets/KisScreenColorPicker.cpp widgets/KoDualColorButton.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp - + widgets/KisNewsWidget.cpp utils/kis_document_aware_spin_box_unit_manager.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_native_gesture_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp input/KisQtWidgetsTweaker.cpp input/KisInputActionGroup.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactory.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.cpp - kis_transaction_based_command.cpp kis_gui_context_command.cpp kis_gui_context_command_p.cpp input/kis_tablet_debugger.cpp input/kis_input_profile_manager.cpp input/kis_input_profile.cpp input/kis_shortcut_configuration.cpp input/config/kis_input_configuration_page.cpp input/config/kis_edit_profiles_dialog.cpp input/config/kis_input_profile_model.cpp input/config/kis_input_configuration_page_item.cpp input/config/kis_action_shortcuts_model.cpp input/config/kis_input_type_delegate.cpp input/config/kis_input_mode_delegate.cpp input/config/kis_input_button.cpp input/config/kis_input_editor_delegate.cpp input/config/kis_mouse_input_editor.cpp input/config/kis_wheel_input_editor.cpp input/config/kis_key_input_editor.cpp processing/fill_processing_visitor.cpp kis_asl_layer_style_serializer.cpp kis_psd_layer_style_resource.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisCloneDocumentStroke.cpp KisNodeDelegate.cpp kis_node_view_visibility_delegate.cpp KisNodeToolTip.cpp KisNodeView.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisFilterEntry.cpp KisImportExportManager.cpp KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoActionsUpdateManager.cpp KisView.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisResourceBundle.cpp KisResourceBundleManifest.cpp kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisSaveGroupVisitor.cpp KisWindowLayoutResource.cpp KisWindowLayoutManager.cpp KisSessionResource.cpp KisReferenceImagesDecoration.cpp KisReferenceImage.cpp flake/KisReferenceImagesLayer.cpp flake/KisReferenceImagesLayer.h + ) if(WIN32) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp qtlockedfile/qtlockedfile_win.cpp input/wintab/kis_tablet_support_win8.cpp opengl/kis_opengl_win.cpp ) endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAsyncAnimationRendererBase.cpp KisAsyncAnimationCacheRenderer.cpp KisAsyncAnimationFramesSavingRenderer.cpp dialogs/KisAsyncAnimationRenderDialogBase.cpp dialogs/KisAsyncAnimationCacheRenderDialog.cpp dialogs/KisAsyncAnimationFramesSaveDialog.cpp canvas/kis_animation_player.cpp kis_animation_importer.cpp KisSyncedAudioPlayback.cpp KisFrameDataSerializer.cpp KisFrameCacheStore.cpp KisFrameCacheSwapper.cpp KisAbstractFrameCacheSwapper.cpp KisInMemoryFrameCacheSwapper.cpp input/wintab/drawpile_tablettester/tablettester.cpp input/wintab/drawpile_tablettester/tablettest.cpp ) if(UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/kis_tablet_event.cpp input/wintab/kis_tablet_support.cpp qtlockedfile/qtlockedfile_unix.cpp ) if(NOT APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/qxcbconnection_xi2.cpp input/wintab/qxcbconnection.cpp input/wintab/kis_xi2_event_filter.cpp ) endif() endif() if(APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} osx.mm ) endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgbookmarkedconfigurationseditor.ui forms/wdgapplyprofile.ui forms/wdgcustompattern.ui forms/wdglayerproperties.ui forms/wdgcolorsettings.ui forms/wdgtabletsettings.ui forms/wdgcolorspaceselector.ui forms/wdgcolorspaceselectoradvanced.ui forms/wdgdisplaysettings.ui forms/kis_previewwidgetbase.ui forms/kis_matrix_widget.ui forms/wdgselectionoptions.ui forms/wdggeometryoptions.ui forms/wdgnewimage.ui forms/wdgimageproperties.ui forms/wdgmaskfromselection.ui forms/wdgmasksource.ui forms/wdgfilterdialog.ui forms/wdgmetadatamergestrategychooser.ui forms/wdgpaintoppresets.ui forms/wdgpaintopsettings.ui forms/wdgdlggeneratorlayer.ui forms/wdgdlgfilelayer.ui forms/wdgfilterselector.ui forms/wdgfilternodecreation.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgsavebrushpreset.ui forms/wdgpreseticonlibrary.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui forms/wdgsessionmanager.ui forms/wdgnewwindowlayout.ui forms/KisWelcomePage.ui + forms/KisNewsPage.ui brushhud/kis_dlg_brush_hud_config.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui input/wintab/drawpile_tablettester/tablettest.ui + ) QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h) add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES} ) if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (HAVE_KIO) target_link_libraries(kritaui KF5::KIOCore) endif() if (NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB} ${XCB_LIBRARIES}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) target_link_libraries(kritaui ${APPKIT_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras) endif() if(X11_FOUND) target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES}) endif() target_include_directories(kritaui PUBLIC $ $ $ $ $ $ $ ) set_target_properties(kritaui PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS}) if (APPLE) install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita) endif () diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index 39051aec42..5a97094002 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,823 +1,823 @@ /* * Copyright (C) 1998, 1999 Torben Weis * 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 #ifdef Q_OS_OSX #include "osx.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 #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 "kis_spin_box_unit_manager.h" #include "kis_document_aware_spin_box_unit_manager.h" #include "KisViewManager.h" #include "kis_workspace_resource.h" #include #include #include "widgets/KisScreenColorPicker.h" #include "KisDlgInternalColorSelector.h" namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplication::Private { public: Private() {} QPointer splashScreen; KisAutoSaveRecoveryDialog *autosaveDialog {0}; QPointer mainWindow; // The first mainwindow we create on startup bool batchRun {false}; }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash, int fileCount) : m_splash(splash) , m_fileCount(fileCount) { } ~ResetStarting() { if (m_splash) { m_splash->hide(); } } QPointer m_splash; int m_fileCount; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) , d(new Private) { #ifdef Q_OS_OSX setMouseCoalescingEnabled(false); #endif KisDlgInternalColorSelector::s_screenColorPickerFactory = KisScreenColorPicker::createScreenColorPicker; 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"); 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(); } #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 KisApplication::initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void KisApplication::addResourceTypes() { // qDebug() << "addResourceTypes();"; // All Krita's resource types KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); 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("kis_windowlayouts", "data", "/windowlayouts/"); KoResourcePaths::addResourceType("kis_sessions", "data", "/sessions/"); 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("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("ko_effects", "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); KoResourcePaths::addResourceType("symbols", "data", "/symbols"); KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons"); KoResourcePaths::addResourceType("ko_gamutmasks", "data", "/gamutmasks/", true); // // 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) + "/brushes/"); 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/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/color-schemes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gamutmasks/"); } void KisApplication::loadResources() { // qDebug() << "loadResources();"; setSplashScreenLoadingText(i18n("Loading Resources...")); processEvents(); KoResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brush Presets...")); processEvents(); KisResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brushes...")); processEvents(); KisBrushServer::instance()->brushServer(); setSplashScreenLoadingText(i18n("Loading Bundles...")); processEvents(); KisResourceBundleServerProvider::instance(); } void KisApplication::loadResourceTags() { // qDebug() << "loadResourceTags()"; KoResourceServerProvider::instance()->patternServer()->loadTags(); KoResourceServerProvider::instance()->gradientServer()->loadTags(); KoResourceServerProvider::instance()->paletteServer()->loadTags(); KoResourceServerProvider::instance()->svgSymbolCollectionServer()->loadTags(); KisBrushServer::instance()->brushServer()->loadTags(); KisResourceServerProvider::instance()->workspaceServer()->loadTags(); KisResourceServerProvider::instance()->layerStyleCollectionServer()->loadTags(); KisResourceBundleServerProvider::instance()->resourceBundleServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->clearOldSystemTags(); } void KisApplication::loadPlugins() { // qDebug() << "loadPlugins();"; KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); } void KisApplication::loadGuiPlugins() { // qDebug() << "loadGuiPlugins();"; // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); processEvents(); // qDebug() << "loading tools"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); processEvents(); // qDebug() << "loading dockers"; 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(); // qDebug() << "loading exiv2"; KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg(false); #if defined(Q_OS_WIN) #ifdef ENV32BIT 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 QString opengl = cfg.canvasState(); if (opengl == "OPENGL_NOT_TRIED" ) { cfg.setCanvasState("TRY_OPENGL"); } else if (opengl != "OPENGL_SUCCESS") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doNewImage = args.doNewImage(); const bool doTemplate = args.doTemplate(); const bool exportAs = args.exportAs(); const QString exportFileName = args.exportFileName(); d->batchRun = (exportAs || !exportFileName.isEmpty()); const bool needsMainWindow = !exportAs; // only show the mainWindow when no command-line mode option is passed bool showmainWindow = !exportAs; // would be !batchRun; const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load the plugins loadPlugins(); // Load all resources loadResources(); // Load all the tags loadResourceTags(); // Load the gui plugins loadGuiPlugins(); KisPart *kisPart = KisPart::instance(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); bool sessionNeeded = true; auto sessionMode = cfg.sessionOnStartup(); if (!args.session().isEmpty()) { sessionNeeded = !kisPart->restoreSession(args.session()); } else if (sessionMode == KisConfig::SOS_ShowSessionManager) { showmainWindow = false; sessionNeeded = false; kisPart->showSessionManager(); } else if (sessionMode == KisConfig::SOS_PreviousSession) { KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session"); const QString &sessionName = sessionCfg.readEntry("previousSession"); sessionNeeded = !kisPart->restoreSession(sessionName); } if (sessionNeeded) { kisPart->startBlankSession(); } if (!args.windowLayout().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->windowLayoutServer(); KisWindowLayoutResource* windowLayout = rserver->resourceByName(args.windowLayout()); if (windowLayout) { windowLayout->applyLayout(); } } if (showmainWindow) { d->mainWindow = kisPart->currentMainwindow(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(args.workspace()); if (workspace) { d->mainWindow->restoreWorkspace(workspace); } } if (args.canvasOnly()) { d->mainWindow->viewManager()->switchCanvasOnly(true); } if (args.fullScreen()) { d->mainWindow->showFullScreen(); } } else { d->mainWindow = kisPart->createMainWindow(); } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test) if (!d->batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.image(); if (doc) { kisPart->addDocument(doc); d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } // Get the command line arguments which we have to parse 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) { // called in mix with batch options? ignore and silently skip if (d->batchRun) { continue; } if (createNewDocFromTemplate(fileName, d->mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return 1; } KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } QTimer::singleShot(0, this, SLOT(quit())); } else if (d->mainWindow) { if (fileName.endsWith(".bundle")) { d->mainWindow->installBundle(fileName); } else { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { // Normal case, success numberOfOpenDocuments++; } } } } } } // 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 if (d->splashScreen) { d->splashScreen->displayLinks(true); d->splashScreen->displayRecentFiles(true); } // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(QString textToLoad) { if (d->splashScreen) { //d->splashScreen->loadingLabel->setText(textToLoad); d->splashScreen->setLoadingText(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()) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mw->openDocument(QUrl::fromLocalFile(filename), flags); } } } } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mainWindow->openDocument(QUrl::fromLocalFile(url), flags); } } void KisApplication::checkAutosaveFiles() { if (d->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 QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Allow the user to make their selection if (autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } d->autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); QDialog::DialogCode result = (QDialog::DialogCode) d->autosaveDialog->exec(); if (result == QDialog::Accepted) { QStringList filesToRecover = d->autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } autosaveFiles = filesToRecover; } else { autosaveFiles.clear(); } if (autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } if (d->mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; d->mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); } } } // cleanup delete d->autosaveDialog; d->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); KisMainWindow::OpenFlags batchFlags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) { 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/KisDocument.cpp b/libs/ui/KisDocument.cpp index 50e74a92ac..47354d515d 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1817 +1,1842 @@ /* This file is part of the Krita project * * 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 "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include "KisReferenceImagesLayer.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" #include "KisCloneDocumentStroke.h" // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *q) : docInfo(new KoDocumentInfo(q)) // deleted by QObject , importExportManager(new KisImportExportManager(q)) // deleted manually , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) // deleted by QObject , m_bAutoDetectedMime(false) , modified(false) , readwrite(true) , firstMod(QDateTime::currentDateTime()) , lastMod(firstMod) , nserver(new KisNameServer(1)) , imageIdleWatcher(2000 /*ms*/) , globalAssistantsColor(KisConfig(true).defaultAssistantsColor()) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *q) : docInfo(new KoDocumentInfo(*rhs.docInfo, q)) , unit(rhs.unit) , importExportManager(new KisImportExportManager(q)) , mimeType(rhs.mimeType) , outputMimeType(rhs.outputMimeType) , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) , guidesConfig(rhs.guidesConfig) , m_bAutoDetectedMime(rhs.m_bAutoDetectedMime) , m_url(rhs.m_url) , m_file(rhs.m_file) , modified(rhs.modified) , readwrite(rhs.readwrite) , firstMod(rhs.firstMod) , lastMod(rhs.lastMod) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document! , globalAssistantsColor(rhs.globalAssistantsColor) , gridConfig(rhs.gridConfig) , savingLock(&savingMutex) , batchMode(rhs.batchMode) { // TODO: clone assistants } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; QColor globalAssistantsColor; KisSharedPtr referenceImagesLayer; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class StrippedSafeSavingLocker; }; class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(rhs.objectName()); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(true), false); if (rhs.d->preActivatedNode) { // since we clone uuid's, we can use them for lacating new // nodes. Otherwise we would need to use findSymmetricClone() d->preActivatedNode = KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid()); } } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer->disconnect(this); d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { slotCompleteSavingDocument(job, KisImportExportFilter::CreationError, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); return false; } KisConfig cfg(true); if (cfg.backupFile() && filePathInfo.exists()) { KBackup::backupFile(job.filePath); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus,QString)), job, exportConfiguration); if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; return exportDocumentImpl(ExportFileJob(url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { if (status == KisImportExportFilter::UserCancelled) return; const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage))); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { + const QString existingAutoSaveBaseName = localFilePath(); + const bool wasRecovered = isRecovered(); + setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { - d->undoStack->setClean(); + /** + * If undo stack is alreado clean/empty, it doesn't emit any + * signals, so we might forget update document modified state + * (which was set, e.g. while recovering an autosave file) + */ + + if (d->undoStack->isClean()) { + setModified(false); + } else { + d->undoStack->setClean(); + } } setRecovered(false); - removeAutoSaveFiles(); + removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->batchMode; } void KisDocument::setFileBatchMode(const bool batchMode) { d->batchMode = batchMode; } KisDocument* KisDocument::lockAndCloneForSaving() { // force update of all the asynchronous nodes before cloning QApplication::processEvents(); KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root()); KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { if (!window->viewManager()->blockUntilOperationsFinished(d->image)) { return 0; } } } Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportFilter::ConversionStatus status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status == KisImportExportFilter::OK; } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { return initiateSavingInBackground(actionName, receiverObject, receiverMethod, job, exportConfiguration, std::unique_ptr()); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument; if (!optionalClonedDocument) { clonedDocument.reset(lockAndCloneForSaving()); } else { clonedDocument.reset(optionalClonedDocument.release()); } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus, const QString&)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus, const QString&))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { // the state should have been deinitialized in slotChildCompletedSavingInBackground() KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } } return started; } void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) { d->savingMutex.unlock(); return; } KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument); if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid()); const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus, const QString&)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0, std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), Qt::BlockingQueuedConnection); KisStrokeId strokeId = d->image->startStroke(stroke); d->image->endStroke(strokeId); setInfiniteAutoSaveInterval(); } else if (!started) { setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotAutoSave() { slotAutoSaveImpl(std::unique_ptr()); } void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) { slotAutoSaveImpl(std::unique_ptr(clonedDocument)); } void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg(true); d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer->stop(); // until the next change d->autoSaveFailureCount = 0; } else { setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } KisImportExportFilter::ConversionStatus initializationStatus; d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage()); return false; } typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, ""); return; } KisImportExportFilter::ConversionStatus status = d->childSavingFuture.result(); const QString errorMessage = this->errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { if (isReadWrite() && delay > 0) { d->autoSaveTimer->start(delay * 1000); } else { d->autoSaveTimer->stop(); } } void KisDocument::setNormalAutoSaveInterval() { setAutoSaveDelay(d->autoSaveDelay); d->autoSaveFailureCount = 0; } void KisDocument::setEmergencyAutoSaveInterval() { const int emergencyAutoSaveInterval = 10; /* sec */ setAutoSaveDelay(emergencyAutoSaveInterval); d->autoSaveFailureCount++; } void KisDocument::setInfiniteAutoSaveInterval() { setAutoSaveDelay(-1); } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QRegularExpression autosavePattern("^\\..+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern.match(filename).hasMatch()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #endif } else { retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } if (ret) { // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

"; if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window && window->viewManager()) { KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } KisImportExportFilter::ConversionStatus status; status = d->importExportManager->importDocument(localFilePath(), typeName); if (status != KisImportExportFilter::OK) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); emit sigLoadingFinished(); undoStack()->clear(); return true; } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; const QString _url(url().fileName()); // if URL is empty...it is probably an unsaved file if (_url.isEmpty()) { c = " [" + i18n("Not Saved") + "] "; } else { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } -void KisDocument::removeAutoSaveFiles() +void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered) { //qDebug() << "removeAutoSaveFiles"; // Eliminate any auto-save file - QString asf = generateAutoSaveFileName(localFilePath()); // the one in the current dir + QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir + //qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf); if (QFile::exists(asf)) { //qDebug() << "\tremoving autosavefile" << asf; QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME + //qDebug() << "Autsavefile in $home" << asf; if (QFile::exists(asf)) { //qDebug() << "\tremoving autsavefile 2" << asf; QFile::remove(asf); } + + QRegularExpression autosavePattern("^\\..+-autosave.kra$"); + + if (wasRecovered && + !autosaveBaseName.isEmpty() && + autosavePattern.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() && + QFile::exists(autosaveBaseName)) { + + QFile::remove(autosaveBaseName); + } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg(true); d->undoStack->setUndoLimit(cfg.undoStackLimit()); d->autoSaveDelay = cfg.autoSaveInterval(); setNormalAutoSaveInterval(); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { d->gridConfig = config; } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) return false; if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) return false; d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisImageSP image; KisPaintLayerSP layer; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); documentInfo()->setAboutInfo("title", name); documentInfo()->setAboutInfo("abstract", description); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); Q_CHECK_PTR(layer); if (backgroundAsLayer) { image->setDefaultProjectionColor(KoColor(cs)); if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) { layer->paintDevice()->setDefaultPixel(bgColor); } else { // Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel KisFillPainter painter; painter.begin(layer->paintDevice()); painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8()); } } else { image->setDefaultProjectionColor(bgColor); } layer->setDirty(QRect(0, 0, width, height)); image->addNode(layer.data(), image->rootLayer().data()); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } KisConfig cfg(false); cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); QApplication::restoreOverrideCursor(); return true; } bool KisDocument::isSaving() const { const bool result = d->savingMutex.tryLock(); if (result) { d->savingMutex.unlock(); } return !result; } void KisDocument::waitForSavingToComplete() { if (isSaving()) { KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); f.waitForMutex(&d->savingMutex); } } KoShapeControllerBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList &value) { d->assistants = value; } KisSharedPtr KisDocument::referenceImagesLayer() const { return d->referenceImagesLayer.data(); } void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage) { if (d->referenceImagesLayer) { d->referenceImagesLayer->disconnect(this); } if (updateImage) { if (layer) { d->image->addNode(layer); } else { d->image->removeNode(d->referenceImagesLayer); } } d->referenceImagesLayer = layer; if (d->referenceImagesLayer) { connect(d->referenceImagesLayer, SIGNAL(sigUpdateCanvas(const QRectF&)), this, SIGNAL(sigReferenceImagesChanged())); } } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate) { if (d->image) { // Disconnect existing sig/slot connections d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); if (forceInitialUpdate) { d->image->initialRefreshGraph(); } } void KisDocument::hackPreliminarySetImage(KisImageSP image) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image); d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); } void KisDocument::setImageModified() { setModified(true); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage; } void KisDocument::setAssistantsGlobalColor(QColor color) { d->globalAssistantsColor = color; } QColor KisDocument::assistantsGlobalColor() { return d->globalAssistantsColor; } QRectF KisDocument::documentBounds() const { QRectF bounds = d->image->bounds(); if (d->referenceImagesLayer) { bounds |= d->referenceImagesLayer->boundingImageRect(); } return bounds; } diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h index ad7298cb49..f463058cdd 100644 --- a/libs/ui/KisDocument.h +++ b/libs/ui/KisDocument.h @@ -1,651 +1,651 @@ /* This file is part of the Krita project * * 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. */ #ifndef KISDOCUMENT_H #define KISDOCUMENT_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kritaui_export.h" #include class QString; class KUndo2Command; class KoUnit; class KoColor; class KoColorSpace; class KoShapeControllerBase; class KoShapeLayer; class KoStore; class KoOdfReadStore; class KoDocumentInfo; class KoDocumentInfoDlg; class KisImportExportManager; class KisUndoStore; class KisPart; class KisGridConfig; class KisGuidesConfig; class QDomDocument; class KisReferenceImagesLayer; #define KIS_MIME_TYPE "application/x-krita" /** * The %Calligra document class * * This class provides some functionality each %Calligra document should have. * * @short The %Calligra document class */ class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase { Q_OBJECT protected: explicit KisDocument(); /** * @brief KisDocument makes a deep copy of the document \p rhs. * The caller *must* ensure that the image is properly * locked and is in consistent state before asking for * cloning. * @param rhs the source document to copy from */ explicit KisDocument(const KisDocument &rhs); public: enum OpenFlag { None = 0, DontAddToRecent = 0x1, RecoveryFile = 0x2 }; Q_DECLARE_FLAGS(OpenFlags, OpenFlag) /** * Destructor. * * The destructor does not delete any attached KisView objects and it does not * delete the attached widget as returned by widget(). */ ~KisDocument() override; /** * @brief reload Reloads the document from the original url * @return the result of loading the document */ bool reload(); /** * @brief creates a clone of the document and returns it. Please make sure that you * hold all the necessary locks on the image before asking for a clone! */ KisDocument* clone(); /** * @brief openUrl Open an URL * @param url The URL to open * @param flags Control specific behavior * @return success status */ bool openUrl(const QUrl &url, OpenFlags flags = None); /** * Opens the document given by @p url, without storing the URL * in the KisDocument. * Call this instead of openUrl() to implement KisMainWindow's * File --> Import feature. * * @note This will call openUrl(). To differentiate this from an ordinary * Open operation (in any reimplementation of openUrl() or openFile()) * call isImporting(). */ bool importDocument(const QUrl &url); /** * Saves the document as @p url without changing the state of the * KisDocument (URL, modified flag etc.). Call this instead of * KisParts::ReadWritePart::saveAs() to implement KisMainWindow's * File --> Export feature. */ bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0); bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0); private: bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); public: /** * @brief Sets whether the document can be edited or is read only. * * This recursively applied to all child documents and * KisView::updateReadWrite is called for every attached * view. */ void setReadWrite(bool readwrite = true); /** * To be preferred when a document exists. It is fast when calling * it multiple times since it caches the result that readNativeFormatMimeType() * delivers. * This comes from the X-KDE-NativeMimeType key in the .desktop file. */ static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; } /// Checks whether a given mimetype can be handled natively. bool isNativeFormat(const QByteArray& mimetype) const; /// Returns a list of the mimetypes considered "native", i.e. which can /// be saved by KisDocument without a filter, in *addition* to the main one static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; } /** * Returns the actual mimetype of the document */ QByteArray mimeType() const override; /** * @brief Sets the mime type for the document. * * When choosing "save as" this is also the mime type * selected by default. */ void setMimeType(const QByteArray & mimeType) override; /** * @return true if file operations should inhibit the option dialog */ bool fileBatchMode() const; /** * @param batchMode if true, do not show the option dialog for file operations. */ void setFileBatchMode(const bool batchMode); /** * Sets the error message to be shown to the user (use i18n()!) * when loading or saving fails. * If you asked the user about something and they chose "Cancel", */ void setErrorMessage(const QString& errMsg); /** * Return the last error message. Usually KisDocument takes care of * showing it; this method is mostly provided for non-interactive use. */ QString errorMessage() const; /** * Sets the warning message to be shown to the user (use i18n()!) * when loading or saving fails. */ void setWarningMessage(const QString& warningMsg); /** * Return the last warning message set by loading or saving. Warnings * mean that the document could not be completely loaded, but the errors * were not absolutely fatal. */ QString warningMessage() const; /** * @brief Generates a preview picture of the document * @note The preview is used in the File Dialog and also to create the Thumbnail */ QPixmap generatePreview(const QSize& size); /** * Tells the document that its title has been modified, either because * the modified status changes (this is done by setModified() ) or * because the URL or the document-info's title changed. */ void setTitleModified(); /** * @brief Sets the document to empty. * * Used after loading a template * (which is not empty, but not the user's input). * * @see isEmpty() */ void setEmpty(bool empty = true); /** * Return a correctly created QDomDocument for this KisDocument, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * @param tagName the name of the tag for the root element * @param version the DTD version (usually the application's version). */ QDomDocument createDomDocument(const QString& tagName, const QString& version) const; /** * Return a correctly created QDomDocument for an old (1.3-style) %Calligra document, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * This static method can be used e.g. by filters. * @param appName the app's instance name, e.g. words, kspread, kpresenter etc. * @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter. * @param version the DTD version (usually the application's version). */ static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version); /** * Loads a document in the native format from a given URL. * Reimplement if your native format isn't XML. * * @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter */ bool loadNativeFormat(const QString & file); /** * Set standard autosave interval that is set by a config file */ void setNormalAutoSaveInterval(); /** * Set emergency interval that autosave uses when the image is busy, * by default it is 10 sec */ void setEmergencyAutoSaveInterval(); /** * Disable autosave */ void setInfiniteAutoSaveInterval(); /** * @return the information concerning this document. * @see KoDocumentInfo */ KoDocumentInfo *documentInfo() const; /** * Performs a cleanup of unneeded backup files */ - void removeAutoSaveFiles(); + void removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered); /** * Returns true if this document or any of its internal child documents are modified. */ bool isModified() const override; /** * @return caption of the document * * Caption is of the form "[title] - [url]", * built out of the document info (title) and pretty-printed * document URL. * If the title is not present, only the URL it returned. */ QString caption() const; /** * Sets the document URL to empty URL * KParts doesn't allow this, but %Calligra apps have e.g. templates * After using loadNativeFormat on a template, one wants * to set the url to QUrl() */ void resetURL(); /** * @internal (public for KisMainWindow) */ void setMimeTypeAfterLoading(const QString& mimeType); /** * Returns the unit used to display all measures/distances. */ KoUnit unit() const; /** * Sets the unit used to display all measures/distances. */ void setUnit(const KoUnit &unit); KisGridConfig gridConfig() const; void setGridConfig(const KisGridConfig &config); /// returns the guides data for this document. const KisGuidesConfig& guidesConfig() const; void setGuidesConfig(const KisGuidesConfig &data); void clearUndoHistory(); /** * Sets the modified flag on the document. This means that it has * to be saved or not before deleting it. */ void setModified(bool _mod); void setRecovered(bool value); bool isRecovered() const; void updateEditingTime(bool forceStoreElapsed); /** * Returns the global undo stack */ KUndo2Stack *undoStack(); /** * @brief importExportManager gives access to the internal import/export manager * @return the document's import/export manager */ KisImportExportManager *importExportManager() const; /** * @brief serializeToNativeByteArray daves the document into a .kra file wtitten * to a memory-based byte-array * @return a byte array containing the .kra file */ QByteArray serializeToNativeByteArray(); /** * @brief isInSaving shown if the document has any (background) saving process or not * @return true if there is some saving in action */ bool isInSaving() const; public Q_SLOTS: /** * Adds a command to the undo stack and executes it by calling the redo() function. * @param command command to add to the undo stack */ void addCommand(KUndo2Command *command); /** * Begins recording of a macro command. At the end endMacro needs to be called. * @param text command description */ void beginMacro(const KUndo2MagicString &text); /** * Ends the recording of a macro command. */ void endMacro(); Q_SIGNALS: /** * This signal is emitted when the unit is changed by setUnit(). * It is common to connect views to it, in order to change the displayed units * (e.g. in the rulers) */ void unitChanged(const KoUnit &unit); /** * Emitted e.g. at the beginning of a save operation * This is emitted by KisDocument and used by KisView to display a statusbar message */ void statusBarMessage(const QString& text, int timeout = 0); /** * Emitted e.g. at the end of a save operation * This is emitted by KisDocument and used by KisView to clear the statusbar message */ void clearStatusBarMessage(); /** * Emitted when the document is modified */ void modified(bool); void titleModified(const QString &caption, bool isModified); void sigLoadingFinished(); void sigSavingFinished(); void sigGuidesConfigChanged(const KisGuidesConfig &config); void sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void sigReferenceImagesChanged(); private Q_SLOTS: void finishExportInBackground(); void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void slotInitiateAsyncAutosaving(KisDocument *clonedDocument); private: friend class KisPart; friend class SafeSavingLocker; bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument); bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); bool startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Activate/deactivate/configure the autosave feature. * @param delay in seconds, 0 to disable */ void setAutoSaveDelay(int delay); /** * Generate a name for the document. */ QString newObjectName(); QString generateAutoSaveFileName(const QString & path) const; /** * Loads a document * * Applies a filter if necessary, and calls loadNativeFormat in any case * You should not have to reimplement, except for very special cases. * * NOTE: this method also creates a new KisView instance! * * This method is called from the KReadOnlyPart::openUrl method. */ bool openFile(); public: bool isAutosaving() const override; public: QString localFilePath() const override; void setLocalFilePath( const QString &localFilePath ); KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const; bool isReadWrite() const; QUrl url() const override; void setUrl(const QUrl &url) override; bool closeUrl(bool promptToSave = true); bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0); /** * Create a new image that has this document as a parent and * replace the current image with this image. */ bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &imageDescription, const double imageResolution); bool isSaving() const; void waitForSavingToComplete(); KisImageWSP image() const; /** * @brief savingImage provides a detached, shallow copy of the original image that must be used when saving. * Any strokes in progress will not be applied to this image, so the result might be missing some data. On * the other hand, it won't block. * * @return a shallow copy of the original image, or 0 is saving is not in progress */ KisImageSP savingImage() const; /** * Set the current image to the specified image and turn undo on. */ void setCurrentImage(KisImageSP image, bool forceInitialUpdate = true); /** * Set the image of the document preliminary, before the document * has completed loading. Some of the document items (shapes) may want * to access image properties (bounds and resolution), so we should provide * it to them even before the entire image is loaded. * * Right now, the only use by KoShapeRegistry::createShapeFromOdf(), remove * after it is deprecated. */ void hackPreliminarySetImage(KisImageSP image); KisUndoStore* createUndoStore(); /** * The shape controller matches internal krita image layers with * the flake shape hierarchy. */ KoShapeControllerBase * shapeController() const; KoShapeLayer* shapeForNode(KisNodeSP layer) const; /** * Set the list of nodes that was marked as currently active. Used *only* * for saving loading. Never use it for tools or processing. */ void setPreActivatedNode(KisNodeSP activatedNode); /** * @return the node that was set as active during loading. Used *only* * for saving loading. Never use it for tools or processing. */ KisNodeSP preActivatedNode() const; /// @return the list of assistants associated with this document QList assistants() const; /// @replace the current list of assistants with @param value void setAssistants(const QList &value); void setAssistantsGlobalColor(QColor color); QColor assistantsGlobalColor(); /** * Get existing reference images layer or null if none exists. */ KisSharedPtr referenceImagesLayer() const; void setReferenceImagesLayer(KisSharedPtr layer, bool updateImage); bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Return the bounding box of the image and associated elements (e.g. reference images) */ QRectF documentBounds() const; Q_SIGNALS: void completed(); void canceled(const QString &); private Q_SLOTS: void setImageModified(); void slotAutoSave(); void slotUndoStackCleanChanged(bool value); void slotConfigChanged(); private: /** * @brief try to clone the image. This method handles all the locking for you. If locking * has failed, no cloning happens * @return cloned document on success, null otherwise */ KisDocument *lockAndCloneForSaving(); QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); QString prettyPathOrUrl() const; bool openUrlInternal(const QUrl &url); void slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument); class Private; Private *const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags) Q_DECLARE_METATYPE(KisDocument*) #endif diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 887c93f20c..5df3912cef 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2710 +1,2657 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_selection_manager.h" #include "kis_icon_utils.h" #ifdef HAVE_KIO #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #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_custom_image_widget.h" #include #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_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #ifdef Q_OS_WIN #include #endif - - -class DockerTitleStyle : public QProxyStyle -{ - public: - DockerTitleStyle(QStyle *baseStyle = nullptr) : QProxyStyle(baseStyle) {} - QPixmap standardPixmap(QStyle::StandardPixmap sp, const QStyleOption *option = nullptr, - const QWidget *widget = nullptr) const override - { - QIcon closeIcon = KisIconUtils::loadIcon("docker_close"); - QPixmap closePixmap = closeIcon.pixmap(QSize(20, 20)); - - QIcon floatIcon = KisIconUtils::loadIcon("docker_float"); - QPixmap floatPixmap = floatIcon.pixmap(QSize(20, 20)); - - - switch (sp) { - case SP_TitleBarNormalButton: - case SP_TitleBarMinButton: - case SP_TitleBarMenuButton: - return floatPixmap; - case SP_DockWidgetCloseButton: - case SP_TitleBarCloseButton: - return closePixmap; - - default: - break; - } - - return QCommonStyle::standardPixmap(sp, option, widget); - } -}; - - - - class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , 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)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { if (id.isNull()) this->id = QUuid::createUuid(); widgetStack->addWidget(welcomePage); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {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 *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; 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(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); 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, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); 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->setViewManager(d->viewManager); } } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); 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(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, 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", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.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); d->toolbarList = 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)); } } 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); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view) { if (d->activeView == view) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } 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); imageView->setSubWindow(subwin); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg(true); 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(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::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(); - - // go through each docker and set style - for (int i = 0; i < dockWidgets().length(); i++) { - dockWidgets().at(i)->setStyle(new DockerTitleStyle); - } } 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) { // 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 (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // new documents aren't saved yet, so we don't need to say it is modified // new files don't have a URL, so we are using that for the check if (!doc->url().isEmpty()) { if ( doc->isModified()) { caption += " [" + i18n("Modified") + "] "; } } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } d->activeView->setWindowTitle(caption); d->activeView->setWindowModified(doc->isModified()); updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) d->saveAction->setToolTip(i18n("Save as %1", doc->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, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { 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, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); KisDocument::OpenFlags openFlags = KisDocument::None; if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : 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::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this); addView(view); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(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(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(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(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::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { 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; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } bool reset_url; if (document->url().isEmpty()) { reset_url = true; saveas = true; } else { reset_url = false; } 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 nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- 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 = QFileInfo(suggestedURL.toLocalFile()).baseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); 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() || isExporting || 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, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty()) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).baseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).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 = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).baseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).baseName() == filename) && (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) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; - document->setUrl(oldURL); - document->setLocalFilePath(oldFile); } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; - document->setUrl(oldURL); - document->setLocalFilePath(oldFile); } } - if (ret && !isExporting) { - document->setRecovered(false); - } - - 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()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; 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 = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible 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(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragEnterEvent(QDragEnterEvent *event) { d->welcomePage->showDropAreaIndicator(true); 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) { d->welcomePage->showDropAreaIndicator(false); if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) { Q_FOREACH (const QUrl &url, event->mimeData()->urls()) { if (url.toLocalFile().endsWith(".bundle")) { bool r = installBundle(url.toLocalFile()); qDebug() << "\t" << r; } else { openDocument(url, None); } } } } 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*/) { d->welcomePage->showDropAreaIndicator(false); if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); 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 = "document-new"; 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 = "tab-new"; 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::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceManager *KisMainWindow::resourceManager() const { return d->viewManager->resourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(KisWorkspaceResource *workspace) { bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } 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() { KisPart::instance()->closeSession(); } 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 = QStandardPaths::writableLocation(QStandardPaths::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, "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(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); 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() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); 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()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } 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; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); 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; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); - dockWidget->setStyle(new DockerTitleStyle); if (lockAllDockers) { if (dockWidget->titleBarWidget()) { dockWidget->titleBarWidget()->setVisible(false); } dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); } 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 = d->windowStateConfig.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(); } 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::slotDocumentTitleModified() { updateCaption(); 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::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ viewManager()->selectionManager()->selectionChanged(); auto *kisPart = KisPart::instance(); auto *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } 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() && doc->image()) { 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->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(w); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QString extensions = d->workspacemodel->extensions(); QStringList mimeTypes; for(const QString &suffix : extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = new KisWorkspaceResource(""); workspace->setDockerState(m_this->saveState()); d->viewManager->resourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); 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 && child->document()) { 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)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } 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()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); 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())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will contiue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } 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(); } KisView* KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->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 Krita 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(); } QPointer KisMainWindow::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()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.baseName(); } 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(); 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())); d->fullScreenMode = 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->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_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())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(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())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); 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); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } 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 = d->windowStateConfig; 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); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::moveEvent(QMoveEvent *e) { /** * For checking if the display number has changed or not we should always use * positional overload, not using QWidget overload. Otherwise we might get * inconsistency, because screenNumber(widget) can return -1, but screenNumber(pos) * will always return the nearest screen. */ const int oldScreen = qApp->desktop()->screenNumber(e->oldPos()); const int newScreen = qApp->desktop()->screenNumber(e->pos()); if (oldScreen != newScreen) { KisConfigNotifier::instance()->notifyConfigChanged(); } } #include diff --git a/libs/ui/KisMultiFeedRSSModel.cpp b/libs/ui/KisMultiFeedRSSModel.cpp index 11028fdefe..9c058053bb 100644 --- a/libs/ui/KisMultiFeedRSSModel.cpp +++ b/libs/ui/KisMultiFeedRSSModel.cpp @@ -1,216 +1,221 @@ /************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** ** GNU Lesser General Public License Usage ** ** 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. ** ** Other Usage ** ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** **************************************************************************/ #include "KisMultiFeedRSSModel.h" #include #include #include #include #include #include #include #include QString shortenHtml(QString html) { html.replace(QLatin1String("")); uint firstParaEndHtml = (uint) html.indexOf(QLatin1String("

"), html.indexOf(QLatin1String("

"))+1); uint firstParaEndBr = (uint) html.indexOf(QLatin1String("request().url(); requestUrl = source.toString(); streamReader.setDevice(reply); RssItemList list; while (!streamReader.atEnd()) { switch (streamReader.readNext()) { case QXmlStreamReader::StartElement: if (streamReader.name() == QLatin1String("item")) list.append(parseItem()); else if (streamReader.name() == QLatin1String("title")) blogName = streamReader.readElementText(); else if (streamReader.name() == QLatin1String("link")) { if (!streamReader.namespaceUri().isEmpty()) break; QString favIconString(streamReader.readElementText()); QUrl favIconUrl(favIconString); favIconUrl.setPath(QLatin1String("favicon.ico")); blogIcon = favIconUrl.toString(); blogIcon = QString(); // XXX: fix the favicon on krita.org! } break; default: break; } } return list; } private: QXmlStreamReader streamReader; QString requestUrl; QString blogIcon; QString blogName; }; MultiFeedRssModel::MultiFeedRssModel(QObject *parent) : QAbstractListModel(parent), m_networkAccessManager(new KisNetworkAccessManager), m_articleCount(0) { connect(m_networkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(appendFeedData(QNetworkReply*)), Qt::QueuedConnection); } MultiFeedRssModel::~MultiFeedRssModel() { } QHash MultiFeedRssModel::roleNames() const { QHash roleNames; roleNames[TitleRole] = "title"; roleNames[DescriptionRole] = "description"; roleNames[PubDateRole] = "pubDate"; roleNames[LinkRole] = "link"; roleNames[BlogNameRole] = "blogName"; roleNames[BlogIconRole] = "blogIcon"; return roleNames; } void MultiFeedRssModel::addFeed(const QString& feed) { const QUrl feedUrl(feed); QMetaObject::invokeMethod(m_networkAccessManager, "getUrl", Qt::QueuedConnection, Q_ARG(QUrl, feedUrl)); } bool sortForPubDate(const RssItem& item1, const RssItem& item2) { return item1.pubDate > item2.pubDate; } void MultiFeedRssModel::appendFeedData(QNetworkReply *reply) { RssReader reader; m_aggregatedFeed.append(reader.parse(reply)); std::sort(m_aggregatedFeed.begin(), m_aggregatedFeed.end(), sortForPubDate); setArticleCount(m_aggregatedFeed.size()); beginResetModel(); endResetModel(); } void MultiFeedRssModel::removeFeed(const QString &feed) { QMutableListIterator it(m_aggregatedFeed); while (it.hasNext()) { RssItem item = it.next(); if (item.source == feed) it.remove(); } setArticleCount(m_aggregatedFeed.size()); } int MultiFeedRssModel::rowCount(const QModelIndex &) const { return m_aggregatedFeed.size(); } QVariant MultiFeedRssModel::data(const QModelIndex &index, int role) const { RssItem item = m_aggregatedFeed.at(index.row()); switch (role) { - case Qt::DisplayRole: // fall through + case Qt::DisplayRole: + { + return QString("" + item.title + "" + "
(" + item.pubDate.toString("MMMM d, yyyy") + ") " + + item.description.left(90).append("...") + "


"); + } case TitleRole: return item.title; case DescriptionRole: return item.description; case PubDateRole: return item.pubDate.toString("dd-MM-yyyy hh:mm"); case LinkRole: return item.link; case BlogNameRole: return item.blogName; case BlogIconRole: return item.blogIcon; } return QVariant(); } diff --git a/libs/ui/KisNetworkAccessManager.cpp b/libs/ui/KisNetworkAccessManager.cpp index 83b5ab9c45..09adb97dd4 100644 --- a/libs/ui/KisNetworkAccessManager.cpp +++ b/libs/ui/KisNetworkAccessManager.cpp @@ -1,51 +1,50 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * 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 */ #include "KisNetworkAccessManager.h" #include #include #include #include #include KisNetworkAccessManager::KisNetworkAccessManager(QObject *parent) : QNetworkAccessManager(parent) { } void KisNetworkAccessManager::getUrl(const QUrl &url) { QNetworkRequest req; req.setUrl(url); get(req); } QNetworkReply* KisNetworkAccessManager::createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) { QString agentStr = QString::fromLatin1("%1/%2 (QNetworkAccessManager %3; %4; %5 bit)") .arg(qApp->applicationName()) .arg(qApp->applicationVersion()) .arg(QSysInfo::prettyProductName()) .arg(QLocale::system().name()) .arg(QSysInfo::WordSize); - qDebug() << "Agent String" << agentStr; QNetworkRequest req(request); req.setRawHeader("User-Agent", agentStr.toLatin1()); return QNetworkAccessManager::createRequest(op, req, outgoingData); } diff --git a/libs/ui/KisView.cpp b/libs/ui/KisView.cpp index a9f988a283..34b7026323 100644 --- a/libs/ui/KisView.cpp +++ b/libs/ui/KisView.cpp @@ -1,1024 +1,1024 @@ /* * 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 "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 "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 "KisViewManager.h" #include "kis_zoom_manager.h" #include "kis_statusbar.h" #include "kis_painting_assistants_decoration.h" #include "KisReferenceImagesDecoration.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" #include "kis_selection_manager.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)) , referenceImagesDecoration(new KisReferenceImagesDecoration(_q, document)) , floatingMessageCompressor(100, KisSignalCompressor::POSTPONE) { } 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; KisReferenceImagesDecorationSP referenceImagesDecoration; bool isCurrent = false; bool showFloatingMessage = false; QPointer savedFloatingMessage; KisSignalCompressor floatingMessageCompressor; QMdiSubWindow *subWindow{nullptr}; 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); QStatusBar * sb = statusBar(); if (sb) { // No statusbar in e.g. konqueror connect(d->document, SIGNAL(statusBarMessage(const QString&, int)), this, SLOT(slotSavingStatusMessage(const QString&, int))); connect(d->document, SIGNAL(clearStatusBarMessage()), this, SLOT(slotClearStatusText())); } d->canvas.setup(); KisConfig cfg(false); d->canvasController.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->canvasController.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); 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->referenceImagesDecoration); d->referenceImagesDecoration->setVisible(true); d->canvas.addDecoration(d->paintingAssistantsDecoration); d->paintingAssistantsDecoration->setVisible(true); d->showFloatingMessage = cfg.showCanvasMessages(); } KisView::~KisView() { if (d->viewManager) { if (d->viewManager->filterManager()->isStrokeRunning()) { d->viewManager->filterManager()->cancel(); } d->viewManager->mainWindow()->notifyChildViewDestroyed(this); } KoToolManager::instance()->removeCanvasController(&d->canvasController); d->canvasController.setCanvas(0); KisPart::instance()->removeView(this); 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); } /** * When current view is changed, currently selected node is also changed, * therefore we should update selection overlay mask */ viewManager()->selectionManager()->selectionChanged(); } bool KisView::isCurrent() const { return d->isCurrent; } 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); 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; } } 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 *insertAsReferenceImage = new QAction(i18n("Insert as Reference Image"), &popup); QAction *insertAsReferenceImages = new QAction(i18n("Insert as Reference Images"), &popup); QAction *cancel = new QAction(i18n("Cancel"), &popup); popup.addAction(insertAsNewLayer); popup.addAction(insertAsNewFileLayer); popup.addAction(openInNewDocument); popup.addAction(insertAsReferenceImage); popup.addAction(insertManyLayers); popup.addAction(insertManyFileLayers); popup.addAction(openManyDocuments); popup.addAction(insertAsReferenceImages); insertAsNewLayer->setEnabled(image() && urls.count() == 1); insertAsNewFileLayer->setEnabled(image() && urls.count() == 1); openInNewDocument->setEnabled(urls.count() == 1); insertAsReferenceImage->setEnabled(image() && urls.count() == 1); insertManyLayers->setEnabled(image() && urls.count() > 1); insertManyFileLayers->setEnabled(image() && urls.count() > 1); openManyDocuments->setEnabled(urls.count() > 1); insertAsReferenceImages->setEnabled(image() && urls.count() > 1); popup.addSeparator(); popup.addAction(cancel); QAction *action = popup.exec(QCursor::pos()); if (action != 0 && action != cancel) { QTemporaryFile *tmp = 0; 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 if (action == openInNewDocument || action == openManyDocuments) { if (mainWindow()) { mainWindow()->openDocument(url, KisMainWindow::None); } } else if (action == insertAsReferenceImage || action == insertAsReferenceImages) { auto *reference = KisReferenceImage::fromFile(url.toLocalFile(), d->viewConverter, this); if (reference) { reference->setPosition(d->viewConverter.imageToDocument(cursorPos)); d->referenceImagesDecoration->addReferenceImage(reference); KoToolManager::instance()->switchToolRequested("ToolReferenceImages"); } } } 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&, int)), this, SLOT(slotSavingStatusMessage(const QString&, int))); 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()); } void KisView::setSubWindow(QMdiSubWindow *subWindow) { d->subWindow = subWindow; } QStatusBar * KisView::statusBar() const { KisMainWindow *mw = mainWindow(); return mw ? mw->statusBar() : 0; } void KisView::slotSavingStatusMessage(const QString &text, int timeout, bool isAutoSaving) { QStatusBar *sb = statusBar(); if (sb) { sb->showMessage(text, timeout); } KisConfig cfg(true); if (!sb || sb->isHidden() || (!isAutoSaving && cfg.forceShowSaveMessages()) || (cfg.forceShowAutosaveMessages() && isAutoSaving)) { viewManager()->showFloatingMessage(text, QIcon()); } } 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 (user visible) view int viewCount = KisPart::instance()->viewCount(document()); if (viewCount > 1 || !isVisible()) { // there are others still, so don't bother the user event->accept(); return; } if (queryClose()) { d->viewManager->statusBar()->setView(0); event->accept(); return; } event->ignore(); } bool KisView::queryClose() { if (!document()) return true; document()->waitForSavingToComplete(); 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()->mimeType() == document()->nativeFormatMimeType()); if (!viewManager()->mainWindow()->saveDocument(document(), !isNative, false)) return false; break; } case QMessageBox::No : { KisImageSP image = document()->image(); image->requestStrokeCancellation(); viewManager()->blockUntilOperationsFinishedForced(image); - document()->removeAutoSaveFiles(); + document()->removeAutoSaveFiles(document()->localFilePath(), document()->isRecovered()); 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::syncLastActiveNodeToDocument() { KisDocument *doc = document(); if (doc) { doc->setPreActivatedNode(d->currentNode); } } void KisView::saveViewState(KisPropertiesConfiguration &config) const { config.setProperty("file", d->document->url()); config.setProperty("window", mainWindow()->windowStateConfig().name()); if (d->subWindow) { config.setProperty("geometry", d->subWindow->saveGeometry().toBase64()); } config.setProperty("zoomMode", (int)zoomController()->zoomMode()); config.setProperty("zoom", d->canvas.coordinatesConverter()->zoom()); d->canvasController.saveCanvasState(config); } void KisView::restoreViewState(const KisPropertiesConfiguration &config) { if (d->subWindow) { QByteArray geometry = QByteArray::fromBase64(config.getString("geometry", "").toLatin1()); d->subWindow->restoreGeometry(QByteArray::fromBase64(geometry)); } qreal zoom = config.getFloat("zoom", 1.0f); int zoomMode = config.getInt("zoomMode", (int)KoZoomMode::ZOOM_PAGE); d->zoomManager.zoomController()->setZoom((KoZoomMode::Mode)zoomMode, zoom); d->canvasController.restoreCanvasState(config); } void KisView::setCurrentNode(KisNodeSP node) { d->currentNode = node; d->canvas.slotTrySwitchShapeManager(); syncLastActiveNodeToDocument(); } 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(); 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(); } void KisView::closeView() { d->subWindow->close(); } diff --git a/libs/ui/KisViewManager.cpp b/libs/ui/KisViewManager.cpp index 89139fd21f..49f340d0e9 100644 --- a/libs/ui/KisViewManager.cpp +++ b/libs/ui/KisViewManager.cpp @@ -1,1392 +1,1392 @@ /* * 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 * 2017 L. E. Segovia * * 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 "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 #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 #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 "KisDecorationsManager.h" #include #include "kis_paintop_box.h" #include #include "KisPart.h" #include "KisPrintJob.h" #include #include "KisResourceServerProvider.h" #include "kis_selection.h" #include "kis_selection_mask.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_icon_utils.h" #include "kis_guides_manager.h" #include "kis_derived_resources.h" #include "dialogs/kis_delayed_save_dialog.h" #include #include "kis_signals_blocker.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, 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, _actionCollection) , mainWindow(0) , showFloatingMessage(true) , currentImageView(0) , canvasResourceProvider(_q) , canvasResourceManager() , guiUpdateCompressor(30, KisSignalCompressor::POSTPONE, _q) , actionCollection(_actionCollection) , mirrorManager(_q) , inputManager(_q) , actionAuthor(0) , showPixelGrid(0) { KisViewManager::initializeResourceManager(&canvasResourceManager); } 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; QPointer persistentImageProgressUpdater; QScopedPointer persistentUnthreadedProgressUpdaterRouter; QPointer persistentUnthreadedProgressUpdater; KisControlFrame controlFrame; KisNodeManager nodeManager; KisImageManager imageManager; KisGridManager gridManager; KisCanvasControlsManager canvasControlsManager; KisDecorationsManager 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; KSelectAction *actionAuthor; // Select action for author profile. KisAction *showPixelGrid; QByteArray canvasState; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QFlags windowFlags; #endif bool blockUntilOperationsFinishedImpl(KisImageSP image, bool force); }; KisViewManager::KisViewManager(QWidget *parent, KActionCollection *_actionCollection) : 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->persistentImageProgressUpdater = d->statusBar.progressUpdater()->startSubtask(1, "", true); // reset state to "completed" d->persistentImageProgressUpdater->setRange(0,100); d->persistentImageProgressUpdater->setValue(100); d->persistentUnthreadedProgressUpdater = d->statusBar.progressUpdater()->startSubtask(1, "", true); // reset state to "completed" d->persistentUnthreadedProgressUpdater->setRange(0,100); d->persistentUnthreadedProgressUpdater->setValue(100); d->persistentUnthreadedProgressUpdaterRouter.reset( new KoProgressUpdater(d->persistentUnthreadedProgressUpdater, KoProgressUpdater::Unthreaded)); d->persistentUnthreadedProgressUpdaterRouter->setAutoNestNames(true); 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(KisPart::instance(), SIGNAL(sigViewAdded(KisView*)), SLOT(slotViewAdded(KisView*))); connect(KisPart::instance(), SIGNAL(sigViewRemoved(KisView*)), SLOT(slotViewRemoved(KisView*))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotUpdateAuthorProfileActions())); connect(KisConfigNotifier::instance(), SIGNAL(pixelGridModeChanged()), SLOT(slotUpdatePixelGridAction())); KisInputProfileManager::instance()->loadProfiles(); KisConfig cfg(true); d->showFloatingMessage = cfg.showCanvasMessages(); 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)); } KisViewManager::~KisViewManager() { KisConfig cfg(false); 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; } void KisViewManager::initializeResourceManager(KoCanvasResourceManager *resourceManager) { resourceManager->addDerivedResourceConverter(toQShared(new KisCompositeOpResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisEffectiveCompositeOpResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisOpacityResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisFlowResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisSizeResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisLodAvailabilityResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisLodSizeThresholdResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisLodSizeThresholdSupportedResourceConverter)); resourceManager->addDerivedResourceConverter(toQShared(new KisEraserModeResourceConverter)); resourceManager->addResourceUpdateMediator(toQShared(new KisPresetUpdateMediator)); } KActionCollection *KisViewManager::actionCollection() const { return d->actionCollection; } void KisViewManager::slotViewAdded(KisView *view) { // WARNING: this slot is called even when a view from another main windows is added! // Don't expect \p view be a child of this view manager! Q_UNUSED(view); if (viewCount() == 0) { d->statusBar.showAllStatusBarItems(); } } void KisViewManager::slotViewRemoved(KisView *view) { // WARNING: this slot is called even when a view from another main windows is removed! // Don't expect \p view be a child of this view manager! Q_UNUSED(view); 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->image()->compositeProgressProxy()->removeProxy(d->persistentImageProgressUpdater); doc->disconnect(this); } d->currentImageView->canvasController()->proxyObject->disconnect(&d->statusBar); d->viewConnections.clear(); } QPointer imageView = qobject_cast(view); d->currentImageView = imageView; if (imageView) { d->softProof->setChecked(imageView->softProofing()); d->gamutCheck->setChecked(imageView->gamutCheck()); // Wait for the async image to have loaded KisDocument* doc = view->document(); if (KisConfig(true).readEntry("EnablePositionLabel", false)) { connect(d->currentImageView->canvasController()->proxyObject, SIGNAL(documentMousePositionChanged(QPointF)), &d->statusBar, SLOT(documentMousePositionChanged(QPointF))); } // Restore the last used brush preset, color and background color. if (first) { KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); QString defaultPresetName = "basic_tip_default"; bool foundTip = false; for (int i=0; iresourceCount(); i++) { KisPaintOpPresetSP resource = rserver->resources().at(i); if (resource->name().toLower().contains("basic_tip_default")) { defaultPresetName = resource->name(); foundTip = true; } else if (foundTip == false && (resource->name().toLower().contains("default") || resource->filename().toLower().contains("default"))) { defaultPresetName = resource->name(); foundTip = true; } } KisConfig cfg(true); QString lastPreset = cfg.readEntry("LastPreset", defaultPresetName); KisPaintOpPresetSP preset = rserver->resourceByName(lastPreset); if (!preset) { preset = rserver->resourceByName(defaultPresetName); } if (!preset && !rserver->resources().isEmpty()) { preset = rserver->resources().first(); } if (preset) { paintOpBox()->restoreResource(preset.data()); } } 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)) ); // set up progrress reporting doc->image()->compositeProgressProxy()->addProxy(d->persistentImageProgressUpdater); d->viewConnections.addUniqueConnection(&d->statusBar, SIGNAL(sigCancellationRequested()), doc->image(), SLOT(requestStrokeCancellation())); d->viewConnections.addUniqueConnection(d->showPixelGrid, SIGNAL(toggled(bool)), canvasController, SLOT(slotTogglePixelGrid(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->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( d->currentImageView->zoomManager()->zoomController(), SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)), resourceProvider(), SLOT(slotOnScreenResolutionChanged())); } d->actionManager.updateGUI(); 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; } KisPaintopBox* KisViewManager::paintOpBox() const { return d->controlFrame.paintopBox(); } QPointer KisViewManager::createUnthreadedUpdater(const QString &name) { return d->persistentUnthreadedProgressUpdaterRouter->startSubtask(1, name, false); } QPointer KisViewManager::createThreadedUpdater(const QString &name) { return d->statusBar.progressUpdater()->startSubtask(1, name, false); } 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) { KisSelectionMaskSP mask = layer->selectionMask(); if (mask) { return mask->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(true); 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())); actionManager()->createAction("ruler_pixel_multiple2"); 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(); d->showPixelGrid = actionManager()->createAction("view_pixel_grid"); slotUpdatePixelGridAction(); } 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()); } 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; } KisDocument *KisViewManager::document() const { if (d->currentImageView && d->currentImageView->document()) { return d->currentImageView->document(); } return 0; } int KisViewManager::viewCount() const { KisMainWindow *mw = qobject_cast(d->mainWindow); if (mw) { return mw->viewCount(); } return 0; } bool KisViewManager::KisViewManagerPrivate::blockUntilOperationsFinishedImpl(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::blockUntilOperationsFinished(KisImageSP image) { return d->blockUntilOperationsFinishedImpl(image, false); } void KisViewManager::blockUntilOperationsFinishedForced(KisImageSP image) { d->blockUntilOperationsFinishedImpl(image, true); } void KisViewManager::slotCreateTemplate() { if (!document()) return; KisTemplateCreateDia::createTemplate( QStringLiteral("templates/"), ".kra", document(), mainWindow()); } void KisViewManager::slotCreateCopy() { KisDocument *srcDoc = document(); if (!srcDoc) return; if (!this->blockUntilOperationsFinished(srcDoc->image())) return; KisDocument *doc = 0; { KisImageBarrierLocker l(srcDoc->image()); doc = srcDoc->clone(); } KIS_SAFE_ASSERT_RECOVER_RETURN(doc); QString name = srcDoc->documentInfo()->aboutInfo("name"); if (name.isEmpty()) { name = document()->url().toLocalFile(); } name = i18n("%1 (Copy)", name); doc->documentInfo()->setAboutInfo("title", name); 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; if (document()->url().isEmpty()) { KisMainWindow *mw = qobject_cast(d->mainWindow); mw->saveDocument(document(), true, false); 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 { // TODO: this will not work with files extensions like jp2 // ...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()->mimeType(), true); document()->setFileBatchMode(false); if (mainWindow()) { mainWindow()->updateCaption(); } } void KisViewManager::slotSaveIncrementalBackup() { if (!document()) return; if (document()->url().isEmpty()) { KisMainWindow *mw = qobject_cast(d->mainWindow); mw->saveDocument(document(), true, false); 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), document()->mimeType(), true); 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()->mimeType(), true); 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(false); cfg.setShowStatusBar(toggled); } } void KisViewManager::switchCanvasOnly(bool toggled) { KisConfig cfg(false); 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 QT_VERSION < QT_VERSION_CHECK(5, 10, 0) d->windowFlags = main->windowState(); #endif } 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")); 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()); } } } // QT in windows does not return to maximized upon 4th tab in a row // https://bugreports.qt.io/browse/QTBUG-57882, https://bugreports.qt.io/browse/QTBUG-52555, https://codereview.qt-project.org/#/c/185016/ if (cfg.hideTitlebarFullscreen() && !cfg.fullscreenMode()) { if(toggled) { main->setWindowState( main->windowState() | Qt::WindowFullScreen); } else { main->setWindowState( main->windowState() & ~Qt::WindowFullScreen); #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) // If window was maximized prior to fullscreen, restore that if (d->windowFlags & Qt::WindowMaximized) { main->setWindowState( main->windowState() | Qt::WindowMaximized); } #endif } } 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) { QObjectList objects; objects.append(dock); while (!objects.isEmpty()) { QObject* object = objects.takeFirst(); objects.append(object->children()); KisIconUtils::updateIconCommon(object); } } } } void KisViewManager::initializeStatusBarVisibility() { KisConfig cfg(true); 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(true); 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(false); cfg.setShowRulers(value); } void KisViewManager::slotSaveRulersTrackMouseState(bool value) { KisConfig cfg(false); cfg.setRulersTrackMouse(value); } void KisViewManager::setShowFloatingMessage(bool show) { d->showFloatingMessage = show; } void KisViewManager::changeAuthorProfile(const QString &profileName) { KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author"); if (profileName.isEmpty() || profileName == i18nc("choice for author profile", "Anonymous")) { appAuthorGroup.writeEntry("active-profile", ""); } 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(i18nc("choice for author profile", "Anonymous")); KConfigGroup authorGroup(KSharedConfig::openConfig(), "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); QString authorInfo = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/authorinfo/"; QStringList filters = QStringList() << "*.authorinfo"; QDir dir(authorInfo); Q_FOREACH(QString entry, dir.entryList(filters)) { int ln = QString(".authorinfo").size(); entry.chop(ln); if (!profiles.contains(entry)) { profiles.append(entry); } } Q_FOREACH (const QString &profile , profiles) { d->actionAuthor->addAction(profile); } KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author"); QString profileName = appAuthorGroup.readEntry("active-profile", ""); if (profileName == "anonymous" || profileName.isEmpty()) { d->actionAuthor->setCurrentItem(0); } else if (profiles.contains(profileName)) { d->actionAuthor->setCurrentAction(profileName); } } void KisViewManager::slotUpdatePixelGridAction() { KIS_SAFE_ASSERT_RECOVER_RETURN(d->showPixelGrid); KisSignalsBlocker b(d->showPixelGrid); KisConfig cfg(true); - d->showPixelGrid->setChecked(cfg.pixelGridEnabled()); + d->showPixelGrid->setChecked(cfg.pixelGridEnabled() && cfg.useOpenGL()); } diff --git a/libs/ui/KisWelcomePageWidget.cpp b/libs/ui/KisWelcomePageWidget.cpp index ea493b7f1a..22950a0796 100644 --- a/libs/ui/KisWelcomePageWidget.cpp +++ b/libs/ui/KisWelcomePageWidget.cpp @@ -1,278 +1,285 @@ /* This file is part of the KDE project * Copyright (C) 2018 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 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 "KisWelcomePageWidget.h" #include #include #include "kis_action_manager.h" #include "kactioncollection.h" #include "kis_action.h" #include "KConfigGroup" #include "KSharedConfig" #include #include #include "kis_icon_utils.h" #include "krita_utils.h" #include "KoStore.h" - +#include "kis_config.h" KisWelcomePageWidget::KisWelcomePageWidget(QWidget *parent) + : QWidget(parent) { - Q_UNUSED(parent); setupUi(this); recentDocumentsListView->viewport()->setAutoFillBackground(false); recentDocumentsListView->setSpacing(2); + + connect(chkShowNews, SIGNAL(toggled(bool)), newsWidget, SLOT(toggleNews(bool))); + + // configure the News area + KisConfig cfg(true); + bool m_getNews = cfg.readEntry("FetchNews", false); + chkShowNews->setChecked(m_getNews); + } KisWelcomePageWidget::~KisWelcomePageWidget() { } void KisWelcomePageWidget::setMainWindow(KisMainWindow* mainWin) { if (mainWin) { mainWindow = mainWin; - // set the shortcut links from actions (only if a shortcut exists) if ( mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString() != "") { newFileLinkShortcut->setText(QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString() + QString(")")); } if (mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString() != "") { openFileShortcut->setText(QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString() + QString(")")); } populateRecentDocuments(); connect(recentDocumentsListView, SIGNAL(clicked(QModelIndex)), this, SLOT(recentDocumentClicked(QModelIndex))); // we need the view manager to actually call actions, so don't create the connections // until after the view manager is set connect(newFileLink, SIGNAL(clicked(bool)), this, SLOT(slotNewFileClicked())); connect(openFileLink, SIGNAL(clicked(bool)), this, SLOT(slotOpenFileClicked())); // URL link connections connect(manualLink, SIGNAL(clicked(bool)), this, SLOT(slotGoToManual())); connect(gettingStartedLink, SIGNAL(clicked(bool)), this, SLOT(slotGettingStarted())); connect(supportKritaLink, SIGNAL(clicked(bool)), this, SLOT(slotSupportKrita())); connect(userCommunityLink, SIGNAL(clicked(bool)), this, SLOT(slotUserCommunity())); connect(kritaWebsiteLink, SIGNAL(clicked(bool)), this, SLOT(slotKritaWebsite())); connect(sourceCodeLink, SIGNAL(clicked(bool)), this, SLOT(slotSourceCode())); connect(clearRecentFilesLink, SIGNAL(clicked(bool)), this, SLOT(slotClearRecentFiles())); connect(poweredByKDELink, SIGNAL(clicked(bool)), this, SLOT(slotKDESiteLink())); slotUpdateThemeColors(); } } void KisWelcomePageWidget::showDropAreaIndicator(bool show) { if (!show) { QString dropFrameStyle = "QFrame#dropAreaIndicator { border: 0px }"; dropFrameBorder->setStyleSheet(dropFrameStyle); } else { QColor textColor = qApp->palette().color(QPalette::Text); QColor backgroundColor = qApp->palette().color(QPalette::Background); QColor blendedColor = KritaUtils::blendColors(textColor, backgroundColor, 0.8); // QColor.name() turns it into a hex/web format QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 2px dotted ").append(blendedColor.name()).append(" }") ; dropFrameBorder->setStyleSheet(dropFrameStyle); } } void KisWelcomePageWidget::slotUpdateThemeColors() { QColor textColor = qApp->palette().color(QPalette::Text); QColor backgroundColor = qApp->palette().color(QPalette::Background); // make the welcome screen labels a subtle color so it doesn't clash with the main UI elements QColor blendedColor = KritaUtils::blendColors(textColor, backgroundColor, 0.8); QString blendedStyle = QString("color: ").append(blendedColor.name()); // what labels to change the color... startTitleLabel->setStyleSheet(blendedStyle); recentDocumentsLabel->setStyleSheet(blendedStyle); helpTitleLabel->setStyleSheet(blendedStyle); manualLink->setStyleSheet(blendedStyle); gettingStartedLink->setStyleSheet(blendedStyle); supportKritaLink->setStyleSheet(blendedStyle); userCommunityLink->setStyleSheet(blendedStyle); kritaWebsiteLink->setStyleSheet(blendedStyle); sourceCodeLink->setStyleSheet(blendedStyle); newFileLinkShortcut->setStyleSheet(blendedStyle); openFileShortcut->setStyleSheet(blendedStyle); clearRecentFilesLink->setStyleSheet(blendedStyle); poweredByKDELink->setStyleSheet(blendedStyle); recentDocumentsListView->setStyleSheet(blendedStyle); newFileLink->setStyleSheet(blendedStyle); openFileLink->setStyleSheet(blendedStyle); // giving the drag area messaging a dotted border QString dottedBorderStyle = QString("border: 2px dotted ").append(blendedColor.name()).append("; color:").append(blendedColor.name()).append( ";"); dragImageHereLabel->setStyleSheet(dottedBorderStyle); // make drop area QFrame have a dotted line dropFrameBorder->setObjectName("dropAreaIndicator"); QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 4px dotted ").append(blendedColor.name()).append("}"); dropFrameBorder->setStyleSheet(dropFrameStyle); // only show drop area when we have a document over the empty area showDropAreaIndicator(false); // add icons for new and open settings to make them stand out a bit more openFileLink->setIconSize(QSize(30, 30)); newFileLink->setIconSize(QSize(30, 30)); openFileLink->setIcon(KisIconUtils::loadIcon("document-open")); newFileLink->setIcon(KisIconUtils::loadIcon("document-new")); // needed for updating icon color for files that don't have a preview if (mainWindow) { populateRecentDocuments(); } } void KisWelcomePageWidget::populateRecentDocuments() { // grab recent files data recentFilesModel = new QStandardItemModel(); int recentDocumentsIterator = mainWindow->recentFilesUrls().length() > 5 ? 5 : mainWindow->recentFilesUrls().length(); // grab at most 5 for (int i = 0; i < recentDocumentsIterator; i++ ) { QStandardItem *recentItem = new QStandardItem(1,2); // 1 row, 1 column QString recentFileUrlPath = mainWindow->recentFilesUrls().at(i).toString(); QString fileName = recentFileUrlPath.split("/").last(); // get thumbnail -- almost all Krita-supported formats save a thumbnail // this was mostly copied from the KisAutoSaveRecovery file QScopedPointer store(KoStore::createStore(QUrl(recentFileUrlPath), KoStore::Read)); if (store) { if (store->open(QString("Thumbnails/thumbnail.png")) || store->open(QString("preview.png"))) { QByteArray bytes = store->read(store->size()); store->close(); QImage img; img.loadFromData(bytes); recentItem->setIcon(QIcon(QPixmap::fromImage(img))); } else { recentItem->setIcon(KisIconUtils::loadIcon("document-export")); } } else { recentItem->setIcon(KisIconUtils::loadIcon("document-export")); } // set the recent object with the data recentItem->setText(fileName); // what to display for the item recentItem->setToolTip(recentFileUrlPath); recentFilesModel->appendRow(recentItem); } // hide clear and Recent files title if there are none bool hasRecentFiles = mainWindow->recentFilesUrls().length() > 0; recentDocumentsLabel->setVisible(hasRecentFiles); clearRecentFilesLink->setVisible(hasRecentFiles); recentDocumentsListView->setIconSize(QSize(40, 40)); recentDocumentsListView->setModel(recentFilesModel); } void KisWelcomePageWidget::recentDocumentClicked(QModelIndex index) { QString fileUrl = index.data(Qt::ToolTipRole).toString(); mainWindow->openDocument(QUrl(fileUrl), KisMainWindow::None ); } void KisWelcomePageWidget::slotNewFileClicked() { mainWindow->slotFileNew(); } void KisWelcomePageWidget::slotOpenFileClicked() { mainWindow->slotFileOpen(); } void KisWelcomePageWidget::slotClearRecentFiles() { mainWindow->clearRecentFiles(); mainWindow->reloadRecentFileList(); populateRecentDocuments(); } void KisWelcomePageWidget::slotGoToManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisWelcomePageWidget::slotGettingStarted() { QDesktopServices::openUrl(QUrl("https://docs.krita.org/en/user_manual/getting_started.html")); } void KisWelcomePageWidget::slotSupportKrita() { QDesktopServices::openUrl(QUrl("https://krita.org/en/support-us/donations/")); } void KisWelcomePageWidget::slotUserCommunity() { QDesktopServices::openUrl(QUrl("https://forum.kde.org/viewforum.php?f=136")); } void KisWelcomePageWidget::slotKritaWebsite() { QDesktopServices::openUrl(QUrl("https://www.krita.org")); } void KisWelcomePageWidget::slotSourceCode() { QDesktopServices::openUrl(QUrl("https://phabricator.kde.org/source/krita/")); } void KisWelcomePageWidget::slotKDESiteLink() { QDesktopServices::openUrl(QUrl("https://userbase.kde.org/What_is_KDE")); } diff --git a/libs/ui/actions/kis_selection_action_factories.cpp b/libs/ui/actions/kis_selection_action_factories.cpp index 5a0b4bb13b..5952680035 100644 --- a/libs/ui/actions/kis_selection_action_factories.cpp +++ b/libs/ui/actions/kis_selection_action_factories.cpp @@ -1,649 +1,649 @@ /* * 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 "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 "commands_new/kis_transaction_based_command.h" #include "kis_selection_filters.h" #include "kis_shape_selection.h" #include "kis_shape_layer.h" #include #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_keyframe_channel.h" #include #include #include "kis_figure_painting_tool_helper.h" #include "kis_update_outline_job.h" namespace ActionHelper { void copyFromDevice(KisViewManager *view, KisPaintDeviceSP device, bool makeSharpClip = false, const KisTimeRange &range = KisTimeRange()) { 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(), range); } } 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()->clear(); 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 KisDeselectActiveSelectionCommand(view->selection(), 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 KisReselectActiveSelectionCommand(view->activeNode(), 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; } KisTimeRange range; KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (channel) { const int currentTime = image->animationInterface()->currentTime(); range = channel->affectedFrames(currentTime); } ActionHelper::copyFromDevice(view, dev, makeSharpClip, range); } 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->blockUntilOperationsFinished(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 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(); doc->documentInfo()->setAboutInfo("title", i18n("Untitled")); 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 KisInvertSelectionOperation::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()) { view->showFloatingMessage(i18nc("floating message", "Selection is already in a vector format "), QIcon(), 2000, KisFloatingMessage::Low); return; } if (!selection->outlineCacheValid()) { view->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, false, Qt::transparent)); if (!view->blockUntilOperationsFinished(view->image())) { 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, 0), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisSelectionToRasterActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (!selection->hasShapeSelection()) { view->showFloatingMessage(i18nc("floating message", "Selection is already in a raster format "), QIcon(), 2000, KisFloatingMessage::Low); return; } KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection")); struct RasterizeSelection : public KisTransactionBasedCommand { RasterizeSelection(KisSelectionSP sel) : m_sel(sel) {} KisSelectionSP m_sel; KUndo2Command* paint() override { // just create an empty transaction: it will rasterize the // selection and emit the necessary signals KisTransaction transaction(m_sel->pixelSelection()); return transaction.endAndTake(); } }; ap->applyCommand(new RasterizeSelection(selection), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisShapesToVectorSelectionActionFactory::run(KisViewManager* view) { const QList originalShapes = view->canvasBase()->shapeManager()->selection()->selectedShapes(); bool hasSelectionShapes = false; QList clonedShapes; Q_FOREACH (KoShape *shape, originalShapes) { if (dynamic_cast(shape->userData())) { hasSelectionShapes = true; continue; } clonedShapes << shape->cloneShape(); } if (clonedShapes.isEmpty()) { if (hasSelectionShapes) { view->showFloatingMessage(i18nc("floating message", "The shape already belongs to a selection"), QIcon(), 2000, KisFloatingMessage::Low); } return; } KisSelectionToolHelper helper(view->canvasBase(), kundo2_i18n("Convert shapes to vector selection")); helper.addSelectionShapes(clonedShapes); } 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(); KoShapeStrokeSP 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->paintDevice()) { 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); KoShapeStrokeSP 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->paintDevice()) { 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_canvas_decoration.cc b/libs/ui/canvas/kis_canvas_decoration.cc index 30c73de42d..2e847edd61 100644 --- a/libs/ui/canvas/kis_canvas_decoration.cc +++ b/libs/ui/canvas/kis_canvas_decoration.cc @@ -1,94 +1,110 @@ /* * Copyright (c) 2008 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_canvas_decoration.h" #include "kis_canvas2.h" #include "kis_debug.h" struct KisCanvasDecoration::Private { bool visible; QPointer view; QString id; + int priority = 0; }; KisCanvasDecoration::KisCanvasDecoration(const QString& id, QPointerparent) : QObject(parent) , d(new Private) { d->visible = false; d->view = parent; d->id = id; } KisCanvasDecoration::~KisCanvasDecoration() { delete d; } void KisCanvasDecoration::setView(QPointerimageView) { d->view = imageView; } const QString& KisCanvasDecoration::id() const { return d->id; } void KisCanvasDecoration::setVisible(bool v) { d->visible = v; if (d->view && d->view->canvasBase()) { d->view->canvasBase()->updateCanvas(); } } bool KisCanvasDecoration::visible() const { return d->visible; } void KisCanvasDecoration::toggleVisibility() { setVisible(!visible()); } void KisCanvasDecoration::paint(QPainter& gc, const QRectF& updateArea, const KisCoordinatesConverter *converter, KisCanvas2 *canvas = 0) { if (!canvas) { dbgFile<<"canvas does not exist:"<priority; +} + +void KisCanvasDecoration::setPriority(int value) +{ + d->priority = value; +} + +bool KisCanvasDecoration::comparePriority(KisCanvasDecorationSP decoration1, KisCanvasDecorationSP decoration2) +{ + return decoration1->priority() < decoration2->priority(); +} + QPointerKisCanvasDecoration::imageView() { return d->view; } QPointerKisCanvasDecoration::view() const { return d->view; } diff --git a/libs/ui/canvas/kis_canvas_decoration.h b/libs/ui/canvas/kis_canvas_decoration.h index e1f351830f..da49f69285 100644 --- a/libs/ui/canvas/kis_canvas_decoration.h +++ b/libs/ui/canvas/kis_canvas_decoration.h @@ -1,90 +1,105 @@ /* * Copyright (c) 2008 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_CANVAS_DECORATION_H_ #define _KIS_CANVAS_DECORATION_H_ #include #include #include #include #include "KisView.h" #include class KisCanvas2; class QRectF; class QPainter; class KisCoordinatesConverter; class KisCanvasDecoration; typedef KisSharedPtr KisCanvasDecorationSP; /** * This class is the base class for object that draw a decoration on the canvas, * for instance, selections, grids, tools, ... */ class KRITAUI_EXPORT KisCanvasDecoration : public QObject, public KisShared { Q_OBJECT public: KisCanvasDecoration(const QString& id, QPointerparent); ~KisCanvasDecoration() override; void setView(QPointer imageView); const QString& id() const; /** * @return whether the decoration is visible. */ bool visible() const; /** * Will paint the decoration on the QPainter, if the visible is set to true. * * @param updateRect dirty rect in document pixels */ void paint(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter,KisCanvas2* canvas); + /** + * Return z-order priority of the decoration. The higher the priority, the higher + * the decoration is painted. + */ + int priority() const; + + static bool comparePriority(KisCanvasDecorationSP decoration1, KisCanvasDecorationSP decoration2); + public Q_SLOTS: /** * Set if the decoration is visible or not. */ virtual void setVisible(bool v); /** * If decoration is visible, hide it, if not show it. */ void toggleVisibility(); protected: virtual void drawDecoration(QPainter& gc, const QRectF& updateArea, const KisCoordinatesConverter *converter,KisCanvas2* canvas) = 0; /// XXX: unify view and imageview! QPointerimageView(); /** * @return the parent KisView */ QPointer view() const; + + /** + * Set the priority of the decoration. The higher the priority, the higher + * the decoration is painted. + */ + void setPriority(int value); + private: struct Private; Private* const d; }; #endif diff --git a/libs/ui/canvas/kis_canvas_widget_base.cpp b/libs/ui/canvas/kis_canvas_widget_base.cpp index 0c666f994e..ebf1e5c057 100644 --- a/libs/ui/canvas/kis_canvas_widget_base.cpp +++ b/libs/ui/canvas/kis_canvas_widget_base.cpp @@ -1,273 +1,275 @@ /* * Copyright (C) 2007, 2010 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. */ #include "kis_canvas_widget_base.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_coordinates_converter.h" #include "kis_canvas_decoration.h" #include "kis_config.h" #include "kis_canvas2.h" #include "KisViewManager.h" #include "kis_selection_manager.h" #include "KisDocument.h" #include "kis_update_info.h" struct KisCanvasWidgetBase::Private { public: Private(KisCanvas2 *newCanvas, KisCoordinatesConverter *newCoordinatesConverter) : canvas(newCanvas) , coordinatesConverter(newCoordinatesConverter) , viewConverter(newCanvas->viewConverter()) , toolProxy(newCanvas->toolProxy()) , ignorenextMouseEventExceptRightMiddleClick(0) , borderColor(Qt::gray) {} QList decorations; KisCanvas2 * canvas; KisCoordinatesConverter *coordinatesConverter; const KoViewConverter * viewConverter; KoToolProxy * toolProxy; QTimer blockMouseEvent; bool ignorenextMouseEventExceptRightMiddleClick; // HACK work around Qt bug not sending tablet right/dblclick http://bugreports.qt.nokia.com/browse/QTBUG-8598 QColor borderColor; }; KisCanvasWidgetBase::KisCanvasWidgetBase(KisCanvas2 * canvas, KisCoordinatesConverter *coordinatesConverter) : m_d(new Private(canvas, coordinatesConverter)) { m_d->blockMouseEvent.setSingleShot(true); } KisCanvasWidgetBase::~KisCanvasWidgetBase() { /** * Clear all the attached decoration. Otherwise they might decide * to process some events or signals after the canvas has been * destroyed */ //5qDeleteAll(m_d->decorations); m_d->decorations.clear(); delete m_d; } void KisCanvasWidgetBase::drawDecorations(QPainter & gc, const QRect &updateWidgetRect) const { if (!m_d->canvas) { dbgFile<<"canvas doesn't exist, in canvas widget base!"; return; } gc.save(); // Setup the painter to take care of the offset; all that the // classes that do painting need to keep track of is resolution gc.setRenderHint(QPainter::Antialiasing); gc.setRenderHint(QPainter::TextAntialiasing); // This option does not do anything anymore with Qt4.6, so don't re-enable it since it seems to break display // http://www.archivum.info/qt-interest@trolltech.com/2010-01/00481/Re:-(Qt-interest)-Is-QPainter::HighQualityAntialiasing-render-hint-broken-in-Qt-4.6.html // gc.setRenderHint(QPainter::HighQualityAntialiasing); gc.setRenderHint(QPainter::SmoothPixmapTransform); gc.save(); gc.setClipRect(updateWidgetRect); QTransform transform = m_d->coordinatesConverter->flakeToWidgetTransform(); gc.setTransform(transform); // Paint the shapes (other than the layers) m_d->canvas->globalShapeManager()->paint(gc, *m_d->viewConverter, false); // draw green selection outlines around text shapes that are edited, so the user sees where they end gc.save(); QTransform worldTransform = gc.worldTransform(); gc.setPen( Qt::green ); Q_FOREACH (KoShape *shape, canvas()->shapeManager()->selection()->selectedShapes()) { if (shape->shapeId() == "ArtisticText" || shape->shapeId() == "TextShapeID") { gc.setWorldTransform(shape->absoluteTransformation(m_d->viewConverter) * worldTransform); KoShape::applyConversion(gc, *m_d->viewConverter); gc.drawRect(QRectF(QPointF(), shape->size())); } } gc.restore(); // Draw text shape over canvas while editing it, that's needs to show the text selection correctly QString toolId = KoToolManager::instance()->activeToolId(); if (toolId == "ArtisticTextTool" || toolId == "TextTool") { gc.save(); gc.setPen(Qt::NoPen); gc.setBrush(Qt::NoBrush); Q_FOREACH (KoShape *shape, canvas()->shapeManager()->selection()->selectedShapes()) { if (shape->shapeId() == "ArtisticText" || shape->shapeId() == "TextShapeID") { KoShapePaintingContext paintContext(canvas(), false); gc.save(); gc.setTransform(shape->absoluteTransformation(m_d->viewConverter) * gc.transform()); canvas()->shapeManager()->paintShape(shape, gc, *m_d->viewConverter, paintContext); gc.restore(); } } gc.restore(); } gc.restore(); // ask the decorations to paint themselves Q_FOREACH (KisCanvasDecorationSP deco, m_d->decorations) { deco->paint(gc, m_d->coordinatesConverter->widgetToDocument(updateWidgetRect), m_d->coordinatesConverter,m_d->canvas); } gc.setTransform(transform); // - some tools do not restore gc, but that is not important here // - we need to disable clipping to draw handles properly gc.setClipping(false); toolProxy()->paint(gc, *m_d->viewConverter); gc.restore(); } void KisCanvasWidgetBase::addDecoration(KisCanvasDecorationSP deco) { m_d->decorations.push_back(deco); + std::stable_sort(m_d->decorations.begin(), m_d->decorations.end(), KisCanvasDecoration::comparePriority); } void KisCanvasWidgetBase::removeDecoration(const QString &id) { for (auto it = m_d->decorations.begin(); it != m_d->decorations.end(); ++it) { if ((*it)->id() == id) { it = m_d->decorations.erase(it); break; } } } KisCanvasDecorationSP KisCanvasWidgetBase::decoration(const QString& id) const { Q_FOREACH (KisCanvasDecorationSP deco, m_d->decorations) { if (deco->id() == id) { return deco; } } return 0; } void KisCanvasWidgetBase::setDecorations(const QList &decorations) { m_d->decorations=decorations; + std::stable_sort(m_d->decorations.begin(), m_d->decorations.end(), KisCanvasDecoration::comparePriority); } QList KisCanvasWidgetBase::decorations() const { return m_d->decorations; } void KisCanvasWidgetBase::setWrapAroundViewingMode(bool value) { Q_UNUSED(value); } QImage KisCanvasWidgetBase::createCheckersImage(qint32 checkSize) { KisConfig cfg(true); if(checkSize < 0) checkSize = cfg.checkSize(); QColor checkColor1 = cfg.checkersColor1(); QColor checkColor2 = cfg.checkersColor2(); QImage tile(checkSize * 2, checkSize * 2, QImage::Format_RGB32); QPainter pt(&tile); pt.fillRect(tile.rect(), checkColor2); pt.fillRect(0, 0, checkSize, checkSize, checkColor1); pt.fillRect(checkSize, checkSize, checkSize, checkSize, checkColor1); pt.end(); return tile; } void KisCanvasWidgetBase::notifyConfigChanged() { KisConfig cfg(true); m_d->borderColor = cfg.canvasBorderColor(); } QColor KisCanvasWidgetBase::borderColor() const { return m_d->borderColor; } KisCanvas2 *KisCanvasWidgetBase::canvas() const { return m_d->canvas; } KisCoordinatesConverter* KisCanvasWidgetBase::coordinatesConverter() const { return m_d->coordinatesConverter; } QVector KisCanvasWidgetBase::updateCanvasProjection(const QVector &infoObjects) { QVector dirtyViewRects; Q_FOREACH (KisUpdateInfoSP info, infoObjects) { dirtyViewRects << this->updateCanvasProjection(info); } return dirtyViewRects; } KoToolProxy *KisCanvasWidgetBase::toolProxy() const { return m_d->toolProxy; } QVariant KisCanvasWidgetBase::processInputMethodQuery(Qt::InputMethodQuery query) const { if (query == Qt::ImMicroFocus) { QRectF rect = m_d->toolProxy->inputMethodQuery(query, *m_d->viewConverter).toRectF(); return m_d->coordinatesConverter->flakeToWidget(rect); } return m_d->toolProxy->inputMethodQuery(query, *m_d->viewConverter); } void KisCanvasWidgetBase::processInputMethodEvent(QInputMethodEvent *event) { m_d->toolProxy->inputMethodEvent(event); } diff --git a/libs/ui/canvas/kis_grid_decoration.cpp b/libs/ui/canvas/kis_grid_decoration.cpp index 901a6a3d3b..b82a257c4d 100644 --- a/libs/ui/canvas/kis_grid_decoration.cpp +++ b/libs/ui/canvas/kis_grid_decoration.cpp @@ -1,225 +1,225 @@ /* * This file is part of Krita * * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2014 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_grid_decoration.h" #include #include #include #include #include #include "kis_grid_config.h" #include "kis_coordinates_converter.h" struct KisGridDecoration::Private { KisGridConfig config; }; KisGridDecoration::KisGridDecoration(KisView* parent) : KisCanvasDecoration("grid", parent), m_d(new Private) { - + setPriority(0); } KisGridDecoration::~KisGridDecoration() { } void KisGridDecoration::setGridConfig(const KisGridConfig &config) { m_d->config = config; } void KisGridDecoration::drawDecoration(QPainter& gc, const QRectF& updateArea, const KisCoordinatesConverter* converter, KisCanvas2* canvas) { if (!m_d->config.showGrid()) return; Q_UNUSED(canvas); QTransform transform = converter->imageToWidgetTransform(); const qreal scale = KoUnit::approxTransformScale(transform); const int minWidgetSize = 3; const int effectiveSize = qMin(m_d->config.spacing().x(), m_d->config.spacing().y()); int scaleCoeff = 1; quint32 subdivision = m_d->config.subdivision(); while (qFloor(scale * scaleCoeff * effectiveSize) <= minWidgetSize) { if (subdivision > 1) { scaleCoeff = subdivision; subdivision = 1; } else { scaleCoeff *= 2; } if (scaleCoeff > 32768) { qWarning() << "WARNING: Grid Scale Coeff is too high! That is surely a bug!"; return; } } const QPen mainPen = m_d->config.penMain(); const QPen subdivisionPen = m_d->config.penSubdivision(); gc.save(); gc.setTransform(transform); gc.setRenderHints(QPainter::Antialiasing, false); gc.setRenderHints(QPainter::HighQualityAntialiasing, false); qreal x1, y1, x2, y2; QRectF imageRect = converter->documentToImage(updateArea) & converter->imageRectInImagePixels(); imageRect.getCoords(&x1, &y1, &x2, &y2); // for angles. This will later be a combobox to select different types of options // also add options to hide specific lines (vertical, horizonta, angle 1, etc int gridType = m_d->config.gridType(); if (gridType == 0) { // rectangle { // vertical lines const int offset = m_d->config.offset().x(); const int step = scaleCoeff * m_d->config.spacing().x(); const int lineIndexFirst = qCeil((x1 - offset) / step); const int lineIndexLast = qFloor((x2 - offset) / step); for (int i = lineIndexFirst; i <= lineIndexLast; i++) { int w = offset + i * step; gc.setPen(i % subdivision == 0 ? mainPen : subdivisionPen); gc.drawLine(QPointF(w, y1),QPointF(w, y2)); } } { // horizontal lines const int offset = m_d->config.offset().y(); const int step = scaleCoeff * m_d->config.spacing().y(); const int lineIndexFirst = qCeil((y1 - offset) / step); const int lineIndexLast = qFloor((y2 - offset) / step); for (int i = lineIndexFirst; i <= lineIndexLast; i++) { int w = offset + i * step; gc.setPen(i % subdivision == 0 ? mainPen : subdivisionPen); gc.drawLine(QPointF(x1, w),QPointF(x2, w)); } } } if (gridType== 1) { // isometric const int offset = m_d->config.offset().x(); const int offsetY = m_d->config.offset().y(); const int step = scaleCoeff * m_d->config.spacing().y(); const int lineIndexFirst = qCeil((y1 - offset) / step); const int cellSpacing = m_d->config.cellSpacing(); gc.setClipping(true); gc.setClipRect(imageRect, Qt::IntersectClip); // left angle { int gridXAngle = m_d->config.angleLeft(); int counter = lineIndexFirst; int bottomRightOfImageY = y2; // this should be the height of the image int finalY = 0; // figure out the spacing based off the angle. The spacing needs to be perpendicular to the angle, // so we need to do a bit of trig to get the correct spacing. float correctedAngleSpacing = cellSpacing; if (gridXAngle > 0) { correctedAngleSpacing = cellSpacing / qCos( qDegreesToRadians((float)gridXAngle)); } while (finalY < bottomRightOfImageY) { int w = (counter * correctedAngleSpacing) - offsetY; gc.setPen(mainPen); // calculate where the ending point will be based off the angle int startingY = w; int horizontalDistance = x2; int length2 = qTan( qDegreesToRadians((float)gridXAngle)) * x2; // qTan takes radians, so convert first before sending it finalY = startingY - length2; gc.drawLine(QPointF(x1, w),QPointF(horizontalDistance, finalY)); counter = counter +1; } } // right angle (almost the same thing, except starting the lines on the right side) { int gridXAngle = m_d->config.angleRight(); // TODO: add another angle property int counter = lineIndexFirst; int bottomLeftOfImageY = y2; int finalY = 0; // figure out the spacing based off the angle float correctedAngleSpacing = cellSpacing; if (gridXAngle > 0) { correctedAngleSpacing = cellSpacing / qCos( qDegreesToRadians((float)gridXAngle)); } while (finalY < bottomLeftOfImageY) { int w = (counter * correctedAngleSpacing) - offsetY - offset; gc.setPen(mainPen); // calculate where the ending point will be based off the angle int startingY = w; int horizontalDistance = x2; // distance is the same (width of the image) int length2 = qTan( qDegreesToRadians((float)gridXAngle)) * horizontalDistance; // qTan takes radians, so convert first before sending it finalY = startingY - length2; gc.drawLine(QPointF(x2, w),QPointF(0, finalY)); counter = counter +1; } } } gc.restore(); } diff --git a/libs/ui/canvas/kis_guides_decoration.cpp b/libs/ui/canvas/kis_guides_decoration.cpp index 39a1469799..2b0aa26c9c 100644 --- a/libs/ui/canvas/kis_guides_decoration.cpp +++ b/libs/ui/canvas/kis_guides_decoration.cpp @@ -1,90 +1,91 @@ /* * 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_guides_decoration.h" #include #include "kis_config.h" #include "kis_guides_config.h" #include "kis_coordinates_converter.h" struct KisGuidesDecoration::Private { KisGuidesConfig guidesConfig; }; KisGuidesDecoration::KisGuidesDecoration(QPointer view) : KisCanvasDecoration(GUIDES_DECORATION_ID, view), m_d(new Private) { + setPriority(90); } KisGuidesDecoration::~KisGuidesDecoration() { } void KisGuidesDecoration::setGuidesConfig(const KisGuidesConfig &value) { m_d->guidesConfig = value; } const KisGuidesConfig& KisGuidesDecoration::guidesConfig() const { return m_d->guidesConfig; } void KisGuidesDecoration::drawDecoration(QPainter &painter, const QRectF& updateArea, const KisCoordinatesConverter *converter, KisCanvas2 *canvas) { Q_UNUSED(canvas); const qreal borderDelta = 2.0; const QPen guidesPen(m_d->guidesConfig.guidesPen()); painter.save(); painter.setPen(guidesPen); painter.setTransform(QTransform()); painter.setRenderHints(QPainter::Antialiasing, false); painter.setRenderHints(QPainter::HighQualityAntialiasing, false); Q_FOREACH (qreal guide, m_d->guidesConfig.horizontalGuideLines()) { if (guide < updateArea.top() - borderDelta || guide > updateArea.bottom() + borderDelta) { continue; } const QPoint p0 = converter->documentToWidget(QPointF(updateArea.left() - borderDelta, guide)).toPoint(); const QPoint p1 = converter->documentToWidget(QPointF(updateArea.right() + borderDelta, guide)).toPoint(); painter.drawLine(p0, p1); } Q_FOREACH (qreal guide, m_d->guidesConfig.verticalGuideLines()) { if (guide < updateArea.left() - borderDelta || guide > updateArea.right() + borderDelta) { continue; } const QPoint p0 = converter->documentToWidget(QPointF(guide, updateArea.top() - borderDelta)).toPoint(); const QPoint p1 = converter->documentToWidget(QPointF(guide, updateArea.bottom() + borderDelta)).toPoint(); painter.drawLine(p0, p1); } painter.restore(); } diff --git a/libs/ui/forms/KisNewsPage.ui b/libs/ui/forms/KisNewsPage.ui new file mode 100644 index 0000000000..6299f98686 --- /dev/null +++ b/libs/ui/forms/KisNewsPage.ui @@ -0,0 +1,27 @@ + + + KisNewsPage + + + + 0 + 0 + 400 + 300 + + + + + 0 + + + 0 + + + + + + + + + diff --git a/libs/ui/forms/KisWelcomePage.ui b/libs/ui/forms/KisWelcomePage.ui index 202a9bcf73..25a4a94bbb 100644 --- a/libs/ui/forms/KisWelcomePage.ui +++ b/libs/ui/forms/KisWelcomePage.ui @@ -1,760 +1,804 @@ KisWelcomePage 0 0 - 650 - 538 + 703 + 469 0 0 false - + QFrame::Box QFrame::Plain 4 0 - - - 10 - - - 10 - - - 10 - - - 10 - - - 15 - - - - - Qt::Horizontal - - - - 5 - 5 - - - - + 5 0 0 30 0 18 Community 0 0 0 0 true Qt::NoFocus User Manual false true Qt::Horizontal QSizePolicy::Expanding 40 5 0 true Qt::NoFocus Getting Started false true Qt::Horizontal 40 5 true Qt::NoFocus Support Krita false true Qt::Horizontal 40 20 true Qt::NoFocus User Community false true Qt::Horizontal 40 20 true Qt::NoFocus Krita Website false true Qt::Horizontal 40 20 0 0 true Qt::NoFocus Source Code false true Qt::Horizontal 40 20 20 true Qt::NoFocus Powered by KDE false true Qt::Horizontal 40 20 0 0 QFrame::Box 2 Drag Image in window to open Qt::AlignCenter true 30 Qt::Horizontal 40 20 Qt::Vertical 20 20 + + + + Qt::Horizontal + + + + 5 + 5 + + + + + + + + Qt::Vertical + + + + 5 + 5 + + + + Qt::Vertical 5 5 Qt::Horizontal 5 5 5 0 20 18 Start 0 true Qt::NoFocus New File false true 20 0 false Qt::Horizontal 40 20 0 30 true Qt::NoFocus Open File false true 20 0 false Qt::Horizontal 40 20 18 Recent Documents 0 0 true Qt::NoFocus Clear false true Qt::Horizontal 5 20 0 0 300 250 true false Qt::NoFocus false QFrame::NoFrame QFrame::Plain 0 false false QAbstractItemView::SelectItems QListView::Snap QListView::Fixed 0 QListView::ListMode true false - - - - Qt::Vertical - - - - 5 - 5 - - - + + + + + + + + + 0 + 0 + + + + + 18 + + + + News + + + 0 + + + + + + + true + + + + 0 + 0 + + + + Show news about Krita: this needs internet to retrieve information from the krita.org website + + + Enabled + + + + + + + + + + + + KisNewsWidget + QWidget +
widgets/KisNewsWidget.h
+ 1 +
+
diff --git a/libs/ui/forms/wdggeneralsettings.ui b/libs/ui/forms/wdggeneralsettings.ui index 1f8e279cab..08397c4109 100644 --- a/libs/ui/forms/wdggeneralsettings.ui +++ b/libs/ui/forms/wdggeneralsettings.ui @@ -1,802 +1,809 @@ WdgGeneralSettings 0 0 759 468 0 0 552 295 - - Qt::LeftToRight - 0 Cursor 10 10 10 10 10 10 0 0 Cursor Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Outline Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter While painting... 3 9 3 3 0 0 200 0 Show outline Use effective outline size Cursor Color: 48 25 Qt::Vertical 20 40 Window 0 0 Multiple Document Mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 1 Subwindows Tabs Background Image (overrides color): 200 0 QFrame::StyledPanel QFrame::Sunken ... 0 0 Clear Window Background: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 - - + + + + Qt::Vertical + + + + 0 + 18 + + + + + + - Don't show contents when moving sub-windows: + General: - + 0 0 - + Don't show contents when moving sub-windows - - + + - Show on-canvas popup messages: + Show on-canvas popup messages - - + + - + Enable Hi-DPI support - + + + + Allow only one instance of Krita + + + + Qt::Vertical 20 40 - - - - - - - - - - - Enable Hi-DPI support: - - - - - - - - - - - - - - Allow only one instance of Krita: - - - Tools 10 10 10 10 10 Tool Options Location (needs restart) In Doc&ker In Tool&bar true Switch Control/Alt Selection Modifiers Enable Touch Painting Kinetic Scrolling (needs restart) Sensitivity (0-100): 0 0 50 0 0 100 1 75 Show Scrollbar Qt::Vertical 20 40 Qt::Horizontal 40 20 Miscellaneous 0 0 When Krita starts Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Save session when Krita closes - - - - 0 - 0 - - - - Qt::RightToLeft - + - Autosave every: - - - true + Autosave: - - - - 0 - 0 - - - - - 75 - 0 - - - - min - - - 1 - - - 1440 - - - 5 - - - 15 - - + + + + + + 0 + 0 + + + + Enabled + + + true + + + + + + + + 0 + 0 + + + + + 75 + 0 + + + + min + + + Every + + + 1 + + + 1440 + + + 5 + + + 15 + + + + Compress .kra files more (slows loading/saving) Create backup file On importing images as layers, convert to the image colorspace 0 0 Undo stack size: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 75 0 0 1000 5 30 0 0 Number of Palette Presets Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Show root layer Warning: if you enable this setting and the file dialogs do weird stuff, do not report a bug. Enable native file dialogs (warning: may not work correctly on some systems) Maximum brush size: 0 0 The maximum diameter of a brush in pixels. px 100 10000 1000 (Needs restart) Qt::Vertical 504 13 0 0 75 0 10 30 KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisColorButton QPushButton
kis_color_button.h
- + + + m_autosaveCheckBox + toggled(bool) + m_autosaveSpinBox + setEnabled(bool) + +
diff --git a/libs/ui/forms/wdgimageproperties.ui b/libs/ui/forms/wdgimageproperties.ui index b2a67d7b18..f74085f363 100644 --- a/libs/ui/forms/wdgimageproperties.ui +++ b/libs/ui/forms/wdgimageproperties.ui @@ -1,400 +1,397 @@ WdgImageProperties 0 0 449 322 New Image 0 Dimensions 12 12 0 0 Width: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter TextLabel 0 0 Height: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter TextLabel 0 0 Resolution: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 TextLabel pixels-per-inch ppi Qt::Horizontal 40 20 0 0 Background Color: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 20 0 0 Background Opacity: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Image Color Space <html><head/><body><p><span style=" font-weight:600;">Note:</span> This changes only the colorspace of the rendered image. To convert the colorspace of the layers, use Convert Image Colorspace.</p></body></html> Qt::RichText true Softproofing Store Softproofing configuration in the image Rendering Intent 0 Perceptual Relative Colorimetric Saturation Absolute Colorimetric - - Qt::LeftToRight - Adaptation State: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter <html><head/><body><p>Set how much you wish to correct the adaptation state. This will affect how <span style=" font-style:italic;">Absolute Colorimetric</span> changes the whites of your image. In Layman's terms: how much do you wish to have the color management correct the paper-color to screen white while using <span style=" font-style:italic;">Absolute Colorimetric</span>?</p></body></html> Qt::Horizontal Gamut Warning: <html><head/><body><p>Black Point compensation matches the darkest color of the source device to the darkest color of the destination device. Relative Colorimetric without Black Point Compensation will show the difference between the darkest values. With blackpoint compensation, black is black.</p></body></html> Black Point Compensation Qt::Vertical 20 40 Annotations Type: 0 0 TextLabel true KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
KisColorButton QPushButton
kis_color_button.h
KisColorSpaceSelector QWidget
widgets/kis_color_space_selector.h
1
diff --git a/libs/ui/forms/wdgselectionoptions.ui b/libs/ui/forms/wdgselectionoptions.ui index 4f5427e9c9..7231b4c46d 100644 --- a/libs/ui/forms/wdgselectionoptions.ui +++ b/libs/ui/forms/wdgselectionoptions.ui @@ -1,195 +1,195 @@ WdgSelectionOptions 0 0 271 106 0 0 0 0 0 0 Intersect ... true false Vector Selection ... true false - Replace (Shortcut R) + Replace ... true true false - Add (Shortcut A) + Add ... true false CrossCursor Mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Action: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Pixel Selection ... true true false - Subtract (Shortcut S) + Subtract ... true false Qt::Horizontal 0 10 Anti-aliasing true KoGroupButton QToolButton
KoGroupButton.h
diff --git a/libs/ui/kis_action.h b/libs/ui/kis_action.h index 8777a09e8b..527de5de1d 100644 --- a/libs/ui/kis_action.h +++ b/libs/ui/kis_action.h @@ -1,133 +1,134 @@ /* * 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 any pixels are selcted (with any kind of selection) SHAPES_SELECTED = 0x0200, ///< Activate if any vector shape is selected ANY_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 SHAPE_SELECTION_WITH_SHAPES = 0x10000, ///< Activate there is a vector selection active PIXEL_SELECTION_WITH_PIXELS = 0x20000, ///< Activate there is a raster selection active }; Q_DECLARE_FLAGS(ActivationFlags, ActivationFlag) enum ActivationCondition { NO_CONDITION = 0, ACTIVE_NODE_EDITABLE = 0x1, ACTIVE_NODE_EDITABLE_PAINT_DEVICE = 0x2, - SELECTION_EDITABLE = 0x4 + SELECTION_EDITABLE = 0x4, + OPENGL_ENABLED = 0x8, }; 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); ~KisAction() override; 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 3ad124c55a..16709928c4 100644 --- a/libs/ui/kis_action_manager.cpp +++ b/libs/ui/kis_action_manager.cpp @@ -1,491 +1,499 @@ /* * 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 "kis_config.h" #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, 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->actionCollection); d->actionCollection->addAction(name, action); action->setParent(d->actionCollection); d->actions.append(action); action->setActionManager(this); } void KisActionManager::takeAction(KisAction* action) { d->actions.removeOne(action); if (!action->objectName().isEmpty()) { 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(); KisActionRegistry *actionRegistry = KisActionRegistry::instance(); // Add extra properties actionRegistry->propertizeAction(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; KisSelectionManager *selectionManager = 0; KisAction::ActivationConditions conditions = KisAction::NO_CONDITION; if (d->viewManager) { node = d->viewManager->activeNode(); selectionManager = d->viewManager->selectionManager(); // 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; } if (d->viewManager->viewCount() > 1) { flags |= KisAction::MULTIPLE_IMAGES; } if (d->viewManager->document() && d->viewManager->document()->isModified()) { flags |= KisAction::CURRENT_IMAGE_MODIFIED; } if (d->viewManager->activeDevice()) { flags |= KisAction::ACTIVE_DEVICE; } } if (d->viewManager->selectionEditable()) { conditions |= KisAction::SELECTION_EDITABLE; } } // 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->haveAnySelectionWithPixels()) { flags |= KisAction::ANY_SELECTION_WITH_PIXELS; } if (selectionManager->haveShapeSelectionWithShapes()) { flags |= KisAction::SHAPE_SELECTION_WITH_SHAPES; } if (selectionManager->haveRasterSelectionWithPixels()) { 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; } } + KisConfig cfg(true); + if (cfg.useOpenGL()) { + conditions |= KisAction::OPENGL_ENABLED; + } // loop through all actions in action manager and determine what should be enabled Q_FOREACH (QPointer action, d->actions) { bool enable; if (action) { 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::safePopulateMenu(QMenu *menu, const QString &actionId, KisActionManager *actionManager) { KIS_SAFE_ASSERT_RECOVER_RETURN(actionManager); KisAction *action = actionManager->actionByName(actionId); KIS_SAFE_ASSERT_RECOVER_RETURN(action); menu->addAction(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::ANY_SELECTION_WITH_PIXELS) { out << " Any 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"; } + if (conditions & KisAction::OPENGL_ENABLED) { + out << " OpenGL is enabled\n"; + } out << "\n\n"; } } } diff --git a/libs/ui/kis_layer_manager.cc b/libs/ui/kis_layer_manager.cc index 4734d034b2..f51ae4c4e4 100644 --- a/libs/ui/kis_layer_manager.cc +++ b/libs/ui/kis_layer_manager.cc @@ -1,952 +1,952 @@ /* * 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 #include #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 "kis_filter_manager.h" #include "kis_node_visitor.h" #include "kis_paint_layer.h" #include "commands/kis_image_commands.h" #include "commands/kis_node_commands.h" #include "kis_change_file_layer_command.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 "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() { KisImageSP image = m_view->image(); KisLayerSP layer = activeLayer(); const bool isGroupLayer = layer && layer->inherits("KisGroupLayer"); m_imageMergeLayer->setText( isGroupLayer ? i18nc("@action:inmenu", "Merge Group") : i18nc("@action:inmenu", "Merge with Layer Below")); m_flattenLayer->setVisible(!isGroupLayer); 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(); if (!layer) return; QList selectedNodes = m_view->nodeManager()->selectedNodes(); const bool multipleLayersSelected = selectedNodes.size() > 1; KisAdjustmentLayerSP alayer = KisAdjustmentLayerSP(dynamic_cast(layer.data())); KisGeneratorLayerSP glayer = KisGeneratorLayerSP(dynamic_cast(layer.data())); KisFileLayerSP flayer = KisFileLayerSP(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 (flayer && !multipleLayersSelected){ QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath(); QString fileNameOld = flayer->fileName(); KisFileLayer::ScalingMethod scalingMethodOld = flayer->scalingMethod(); KisDlgFileLayer dlg(basePath, flayer->name(), m_view->mainWindow()); dlg.setCaption(i18n("File Layer Properties")); dlg.setFileName(fileNameOld); dlg.setScalingMethod(scalingMethodOld); if (dlg.exec() == QDialog::Accepted) { const QString fileNameNew = dlg.fileName(); KisFileLayer::ScalingMethod scalingMethodNew = dlg.scaleToImageResolution(); if(fileNameNew.isEmpty()){ QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified")); return; } flayer->setName(dlg.layerName()); if (fileNameOld!= fileNameNew || scalingMethodOld != scalingMethodNew) { KisChangeFileLayerCmd *cmd = new KisChangeFileLayerCmd(flayer, basePath, fileNameOld, scalingMethodOld, basePath, fileNameNew, scalingMethodNew); m_view->undoAdapter()->addCommand(cmd); } } } 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->prevSibling(); while (parent && !parent->allowAsChild(layer)) { above = above ? above->parent() : source->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->removeNode(source); m_commandsAdapter->addNode(layer, parent, above); 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::convertLayerToFileLayer(KisNodeSP source) { KisImageSP image = m_view->image(); if (!image) return; QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); KoDialog dlg; QWidget *page = new QWidget(&dlg); dlg.setMainWidget(page); QBoxLayout *layout = new QVBoxLayout(page); dlg.setWindowTitle(i18n("Save layers to...")); QLabel *lbl = new QLabel(i18n("Choose the location where the layer will be saved to. The new file layer will then reference this location.")); lbl->setWordWrap(true); layout->addWidget(lbl); KisFileNameRequester *urlRequester = new KisFileNameRequester(page); urlRequester->setMode(KoFileDialog::SaveFile); urlRequester->setMimeTypeFilters(listMimeFilter); urlRequester->setFileName(m_view->document()->url().toLocalFile()); if (m_view->document()->url().isLocalFile()) { QFileInfo location = QFileInfo(m_view->document()->url().toLocalFile()).baseName(); location.setFile(location.dir(), location.baseName()+"_"+ source->name()+".kra"); urlRequester->setFileName(location.absoluteFilePath()); } else { const QFileInfo location = QFileInfo(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); const QString proposedFileName = QDir(location.absoluteFilePath()).absoluteFilePath(source->name() + ".kra"); urlRequester->setFileName(proposedFileName); } layout->addWidget(urlRequester); 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"; } QScopedPointer doc(KisPart::instance()->createDocument()); QRect bounds = source->exactBounds(); KisImageSP dst = new KisImage(doc->createUndoStore(), image->width(), image->height(), image->projection()->compositionSourceColorSpace(), source->name()); dst->setResolution(image->xRes(), image->yRes()); doc->setFileBatchMode(false); doc->setCurrentImage(dst); KisNodeSP node = source->clone(); dst->addNode(node); dst->initialRefreshGraph(); dst->cropImage(bounds); dst->waitForDone(); bool r = doc->exportDocumentSync(QUrl::fromLocalFile(path), mimeType.toLatin1()); if (!r) { qDebug()<< "Path:"<errorMessage(); } else { QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath(); QString relativePath = QDir(basePath).relativeFilePath(path); KisFileLayer *fileLayer = new KisFileLayer(image, basePath, relativePath, KisFileLayer::None, source->name(), OPACITY_OPAQUE_U8); fileLayer->setX(bounds.x()); fileLayer->setY(bounds.y()); KisNodeSP dstParent = source->parent(); KisNodeSP dstAboveThis = source->prevSibling(); m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a file layer")); m_commandsAdapter->removeNode(source); m_commandsAdapter->addNode(fileLayer, dstParent, dstAboveThis); m_commandsAdapter->endMacro(); } doc->closeUrl(false); } 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::flattenImage() { KisImageSP image = m_view->image(); if (!m_view->blockUntilOperationsFinished(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; } bool tryFlattenGroupLayer(KisNodeSP currentNode, KisImageSP image) { bool result = false; if (currentNode->inherits("KisGroupLayer")) { KisGroupLayer *layer = qobject_cast(currentNode.data()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(layer, false); image->flattenLayer(layer); result = true; } return result; } void KisLayerManager::mergeLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(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)) { // already done! } else if (tryFlattenGroupLayer(m_view->activeNode(), image)) { // already done! } else { if (!layer->prevSibling()) return; KisLayer *prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (prevLayer->userLocked()) { m_view->showFloatingMessage( i18nc("floating message in layer manager", "Layer is locked "), QIcon(), 2000, KisFloatingMessage::Low); } else 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() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(image)) return; convertNodeToPaintLayer(layer); m_view->updateGUI(); } void KisLayerManager::rasterizeLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; if (!m_view->blockUntilOperationsFinished(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::supportedMimeTypes(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(), false); if (mimeType.isEmpty()) { mimeType = "image/png"; } QString extension = KisMimeDatabase::suffixesForMimeType(mimeType).first(); QString basename = f.baseName(); 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->blockUntilOperationsFinished(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 bf1eab182d..45acb4cf79 100644 --- a/libs/ui/kis_node_manager.cpp +++ b/libs/ui/kis_node_manager.cpp @@ -1,1533 +1,1519 @@ /* * 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 #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 "KisNodeDisplayModeAdapter.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 "kis_shape_layer.h" #include "processing/kis_mirror_processing_visitor.h" #include "KisView.h" #include 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)) , nodeDisplayModeAdapter(new KisNodeDisplayModeAdapter()) { } KisNodeManager * q; KisViewManager * view; QPointerimageView; KisLayerManager layerManager; KisMaskManager maskManager; KisNodeCommandsAdapter commandsAdapter; QScopedPointer nodeSelectionAdapter; QScopedPointer nodeInsertionAdapter; QScopedPointer nodeDisplayModeAdapter; KisAction *showInTimeline; KisNodeList selectedNodes; QPointer nodeJuggler; KisNodeWSP previouslyActiveNode; bool activateNodeImpl(KisNodeSP node); QSignalMapper nodeCreationSignalMapper; QSignalMapper nodeConversionSignalMapper; void saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity); void mergeTransparencyMaskAsAlpha(bool writeToLayers); KisNodeJugglerCompressed* lazyGetJuggler(const KUndo2MagicString &actionName); }; bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node) { Q_ASSERT(view); Q_ASSERT(view->canvasBase()); Q_ASSERT(view->canvasBase()->globalShapeManager()); Q_ASSERT(imageView); if (node && node == q->activeNode()) { return false; } // Set the selection on the shape manager to the active layer // and set call KoSelection::setActiveLayer( KoShapeLayer* layer ) // with the parent of the active layer. KoSelection *selection = view->canvasBase()->globalShapeManager()->selection(); Q_ASSERT(selection); selection->deselectAll(); if (!node) { selection->setActiveLayer(0); imageView->setCurrentNode(0); maskManager.activateMask(0); layerManager.activateLayer(0); previouslyActiveNode = q->activeNode(); } else { previouslyActiveNode = q->activeNode(); KoShape * shape = view->document()->shapeForNode(node); //if (!shape) return false; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, false); selection->select(shape); KoShapeLayer * shapeLayer = dynamic_cast(shape); //if (!shapeLayer) return false; KIS_SAFE_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"); + KisAction * action = 0; + + 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("mirrorAllNodesX"); + connect(action, SIGNAL(triggered()), this, SLOT(mirrorAllNodesX())); + + action = actionManager->createAction("mirrorAllNodesY"); + connect(action, SIGNAL(triggered()), this, SLOT(mirrorAllNodesY())); + action = actionManager->createAction("activateNextLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode())); action = actionManager->createAction("activatePreviousLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode())); action = actionManager->createAction("switchToPreviouslyActiveNode"); connect(action, SIGNAL(triggered()), this, SLOT(switchToPreviouslyActiveNode())); action = actionManager->createAction("save_node_as_image"); connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage())); action = actionManager->createAction("save_vector_node_to_svg"); connect(action, SIGNAL(triggered()), this, SLOT(saveVectorLayerAsImage())); action->setActivationFlags(KisAction::ACTIVE_SHAPE_LAYER); 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())); action = actionManager->createAction("show_in_timeline"); action->setCheckable(true); connect(action, SIGNAL(toggled(bool)), this, SLOT(slotShowHideTimeline(bool))); m_d->showInTimeline = action; 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"); CONVERT_NODE_ACTION_2("convert_layer_to_file_layer", "KisFileLayer", QStringList()<< "KisFileLayer" << "KisCloneLayer"); connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(const QString &)), this, SLOT(convertNode(const QString &))); action = actionManager->createAction("isolate_layer"); connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool))); action = actionManager->createAction("toggle_layer_visibility"); connect(action, SIGNAL(triggered()), this, SLOT(toggleVisibility())); action = actionManager->createAction("toggle_layer_lock"); connect(action, SIGNAL(triggered()), this, SLOT(toggleLock())); action = actionManager->createAction("toggle_layer_inherit_alpha"); connect(action, SIGNAL(triggered()), this, SLOT(toggleInheritAlpha())); action = actionManager->createAction("toggle_layer_alpha_lock"); connect(action, SIGNAL(triggered()), this, SLOT(toggleAlphaLock())); 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(slotTryRestartIsolatedMode())); } 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()); if (m && m->active() && l && l->selectionMask()) { l->selectionMask()->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(); KisNodeSP activeNode = this->activeNode(); if (checked && activeNode) { // Transform and colorize masks don't have pixel data... if (activeNode->inherits("KisTransformMask") || activeNode->inherits("KisColorizeMask")) return; 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::slotTryRestartIsolatedMode() { KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); if (!isolatedRootNode) return; this->toggleIsolateMode(true); } void KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom) { if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) { return; } KisNodeSP activeNode = this->activeNode(); if (!activeNode) { activeNode = m_d->view->image()->root(); } KIS_ASSERT_RECOVER_RETURN(activeNode); // XXX: make factories for this kind of stuff, // with a registry if (nodeType == "KisPaintLayer") { m_d->layerManager.addLayer(activeNode); } else if (nodeType == "KisGroupLayer") { m_d->layerManager.addGroupLayer(activeNode); } else if (nodeType == "KisAdjustmentLayer") { m_d->layerManager.addAdjustmentLayer(activeNode); } else if (nodeType == "KisGeneratorLayer") { m_d->layerManager.addGeneratorLayer(activeNode); } else if (nodeType == "KisShapeLayer") { m_d->layerManager.addShapeLayer(activeNode); } else if (nodeType == "KisCloneLayer") { m_d->layerManager.addCloneLayer(activeNode); } else if (nodeType == "KisTransparencyMask") { m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false); } else if (nodeType == "KisFilterMask") { m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false); } else if (nodeType == "KisColorizeMask") { m_d->maskManager.createColorizeMask(activeNode); } else if (nodeType == "KisTransformMask") { m_d->maskManager.createTransformMask(activeNode); } else if (nodeType == "KisSelectionMask") { m_d->maskManager.createSelectionMask(activeNode, copyFrom, false); } else if (nodeType == "KisFileLayer") { m_d->layerManager.addFileLayer(activeNode); } } void KisNodeManager::createFromVisible() { KisLayerUtils::newLayerFromVisible(m_d->view->image(), m_d->view->image()->root()->lastChild()); } void KisNodeManager::slotShowHideTimeline(bool value) { Q_FOREACH (KisNodeSP node, selectedNodes()) { node->setUseInTimeline(value); } } 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")); bool result = false; if (nodeType == "KisSelectionMask") { result = m_d->maskManager.createSelectionMask(activeNode, copyFrom, true); } else if (nodeType == "KisFilterMask") { result = m_d->maskManager.createFilterMask(activeNode, copyFrom, false, true); } else if (nodeType == "KisTransparencyMask") { result = m_d->maskManager.createTransparencyMask(activeNode, copyFrom, true); } m_d->commandsAdapter.endMacro(); if (!result) { m_d->view->blockUntilOperationsFinishedForced(m_d->imageView->image()); m_d->commandsAdapter.undoLastCommand(); } } else if (nodeType == "KisFileLayer") { m_d->layerManager.convertLayerToFileLayer(activeNode); } else { warnKrita << "Unsupported node conversion type:" << nodeType; } } void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node) { KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController()); KIS_SAFE_ASSERT_RECOVER_RETURN(dummiesFacade); const bool nodeVisible = !isNodeHidden(node, !m_d->nodeDisplayModeAdapter->showGlobalSelectionMask()); if (!nodeVisible) { return; } 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) { // the node must still be in the graph, some asynchronous // signals may easily break this requirement if (node && !node->graphListener()) { node = 0; } 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) { // the node must still be in the graph, some asynchronous // signals may easily break this requirement if (node && !node->graphListener()) { node = 0; } if (node) { QStringList vectorTools = QStringList() << "InteractionTool" << "KarbonPatternTool" << "KarbonGradientTool" << "KarbonCalligraphyTool" << "CreateShapesTool" << "PathTool"; QStringList pixelTools = QStringList() << "KritaShape/KisToolBrush" << "KritaShape/KisToolDyna" << "KritaShape/KisToolMultiBrush" << "KritaFill/KisToolFill" << "KritaFill/KisToolGradient"; KisSelectionMask *selectionMask = dynamic_cast(node.data()); const bool nodeHasVectorAbilities = node->inherits("KisShapeLayer") || (selectionMask && selectionMask->selection()->hasShapeSelection()); if (nodeHasVectorAbilities) { if (pixelTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("InteractionTool"); } } else { if (vectorTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } } } if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); } 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(); { KisSignalsBlocker b(m_d->showInTimeline); m_d->showInTimeline->setChecked(node->useInTimeline()); } } 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")) || 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(); } KisNodeDisplayModeAdapter *KisNodeManager::nodeDisplayModeAdapter() const { return m_d->nodeDisplayModeAdapter.data(); } bool KisNodeManager::isNodeHidden(KisNodeSP node, bool isGlobalSelectionHidden) { if (dynamic_cast(node.data())) { return true; } if (isGlobalSelectionHidden && dynamic_cast(node.data()) && (!node->parent() || !node->parent()->parent())) { return true; } return false; } 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->isEnded() || !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); + mirrorNode(node, commandName, Qt::Horizontal, m_d->view->selection()); } 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); + mirrorNode(node, commandName, Qt::Vertical, m_d->view->selection()); +} + +void KisNodeManager::mirrorAllNodesX() +{ + KisNodeSP node = m_d->view->image()->root(); + mirrorNode(node, kundo2_i18n("Mirror All Layers X"), + Qt::Vertical, m_d->view->selection()); +} + +void KisNodeManager::mirrorAllNodesY() +{ + KisNodeSP node = m_d->view->image()->root(); + mirrorNode(node, kundo2_i18n("Mirror All Layers Y"), + Qt::Vertical, m_d->view->selection()); } void KisNodeManager::activateNextNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node = activeNode->nextSibling(); while (node && node->childCount() > 0) { node = node->firstChild(); } if (!node && activeNode->parent() && activeNode->parent()->parent()) { node = activeNode->parent(); } while(node && isNodeHidden(node, true)) { node = node->nextSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::activatePreviousNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node; if (activeNode->childCount() > 0) { node = activeNode->lastChild(); } else { node = activeNode->prevSibling(); } while (!node && activeNode->parent()) { node = activeNode->parent()->prevSibling(); activeNode = activeNode->parent(); } while(node && isNodeHidden(node, true)) { node = node->prevSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::switchToPreviouslyActiveNode() { if (m_d->previouslyActiveNode && m_d->previouslyActiveNode->parent()) { slotNonUiActivatedNode(m_d->previouslyActiveNode); } } -void KisNodeManager::rotate(double radians) -{ - if(!m_d->view->image()) return; - - KisNodeSP node = activeNode(); - if (!node) return; - - if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) return; - - m_d->view->image()->rotateNode(node, 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) -{ - if (!m_d->view->image()) return; - - KisNodeSP node = activeNode(); - if (!node) return; - - if(!m_d->view->blockUntilOperationsFinished(m_d->view->image())) return; - - m_d->view->image()->shearNode(node, 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) +void KisNodeManager::mirrorNode(KisNodeSP node, + const KUndo2MagicString& actionName, + Qt::Orientation orientation, + KisSelectionSP selection) { 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); + KisProcessingVisitorSP visitor; + + if (selection) { + visitor = new KisMirrorProcessingVisitor(selection, orientation); + } else { + visitor = new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation); + } + + if (!selection) { + applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); + } else { + applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); + } - 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(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export)); QString filename = dialog.filename(); if (filename.isEmpty()) return; QUrl url = QUrl::fromLocalFile(filename); if (url.isEmpty()) return; QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename, false); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(doc->createUndoStore(), bounds.width(), bounds.height(), device->compositionSourceColorSpace(), defaultName); dst->setResolution(xRes, yRes); doc->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity); paintLayer->paintDevice()->makeCloneFrom(device, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->initialRefreshGraph(); doc->exportDocumentSync(url, mimefilter.toLatin1()); } 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()); } #include "SvgWriter.h" void KisNodeManager::saveVectorLayerAsImage() { KisShapeLayerSP shapeLayer = qobject_cast(activeNode().data()); if (!shapeLayer) { return; } KoFileDialog dialog(m_d->view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage"); dialog.setCaption(i18nc("@title:window", "Export to SVG")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "image/svg+xml", "image/svg+xml"); QString filename = dialog.filename(); if (filename.isEmpty()) return; QUrl url = QUrl::fromLocalFile(filename); if (url.isEmpty()) return; const QSizeF sizeInPx = m_d->view->image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / m_d->view->image()->xRes(), sizeInPx.height() / m_d->view->image()->yRes()); QList shapes = shapeLayer->shapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); if (!writer.save(filename, sizeInPt, true)) { QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not save to svg: %1", filename)); } } 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); while (srcIt.nextPixel() && dstIt.nextPixel()) { quint8 *srcPtr = srcIt.rawData(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->opacityU8(srcPtr); srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1); } 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", parentNode->name()), i18n("Cannot write alpha channel of " "the parent layer \"%1\".\n" "The operation will be cancelled.", 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); while (srcIt.nextPixel() && dstIt.nextPixel()) { quint8 *alpha8Ptr = srcIt.rawData(); quint8 *dstPtr = dstIt.rawData(); dstCS->setOpacity(dstPtr, *alpha8Ptr, 1); } if (writeToLayers) { commandsAdapter.addExtraCommand(transaction->endAndTake()); commandsAdapter.removeNode(node); commandsAdapter.endMacro(); } else { KisImageWSP image = view->image(); QRect saveRect = image->bounds(); saveDeviceAsImage(dstDevice, parentNode->name(), saveRect, image->xRes(), image->yRes(), OPACITY_OPAQUE_U8); } } void KisNodeManager::slotSplitAlphaWrite() { m_d->mergeTransparencyMaskAsAlpha(true); } void KisNodeManager::slotSplitAlphaSaveMerged() { m_d->mergeTransparencyMaskAsAlpha(false); } void KisNodeManager::toggleLock() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; bool isLocked = active->userLocked(); for (auto &node : nodes) { node->setUserLocked(!isLocked); } } void KisNodeManager::toggleVisibility() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; bool isVisible = active->visible(); for (auto &node : nodes) { node->setVisible(!isVisible); node->setDirty(); } } void KisNodeManager::toggleAlphaLock() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; auto layer = qobject_cast(active.data()); if (!layer) { return; } bool isAlphaLocked = layer->alphaLocked(); for (auto &node : nodes) { auto layer = qobject_cast(node.data()); if (layer) { layer->setAlphaLocked(!isAlphaLocked); } } } void KisNodeManager::toggleInheritAlpha() { KisNodeList nodes = this->selectedNodes(); KisNodeSP active = activeNode(); if (nodes.isEmpty() || !active) return; auto layer = qobject_cast(active.data()); if (!layer) { return; } bool isAlphaDisabled = layer->alphaChannelDisabled(); for (auto &node : nodes) { auto layer = qobject_cast(node.data()); if (layer) { layer->disableAlphaChannel(!isAlphaDisabled); node->setDirty(); } } } void KisNodeManager::cutLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); if (nodes.isEmpty()) return; KisClipboard::instance()->setLayers(nodes, m_d->view->image(), false); KUndo2MagicString actionName = kundo2_i18n("Cut Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::copyLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); KisClipboard::instance()->setLayers(nodes, m_d->view->image(), true); } void KisNodeManager::pasteLayersFromClipboard() { const QMimeData *data = KisClipboard::instance()->layersMimeData(); if (!data) return; KisNodeSP activeNode = this->activeNode(); KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(dummiesFacade); const bool copyNode = false; KisImageSP image = m_d->view->image(); KisNodeDummy *parentDummy = dummiesFacade->dummyForNode(activeNode); KisNodeDummy *aboveThisDummy = parentDummy ? parentDummy->lastChild() : 0; KisMimeData::insertMimeLayers(data, image, shapeController, parentDummy, aboveThisDummy, copyNode, nodeInsertionAdapter()); } void KisNodeManager::createQuickGroupImpl(KisNodeJugglerCompressed *juggler, const QString &overrideGroupName, KisNodeSP *newGroup, KisNodeSP *newLastChild) { KisNodeSP active = activeNode(); if (!active) return; KisImageSP image = m_d->view->image(); QString groupName = !overrideGroupName.isEmpty() ? overrideGroupName : image->nextLayerName(); KisGroupLayerSP group = new KisGroupLayer(image.data(), groupName, OPACITY_OPAQUE_U8); KisNodeList nodes1; nodes1 << group; KisNodeList nodes2; nodes2 = KisLayerUtils::sortMergableNodes(image->root(), selectedNodes()); KisLayerUtils::filterMergableNodes(nodes2); if (KisLayerUtils::checkIsChildOf(active, nodes2)) { active = nodes2.first(); } KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; juggler->addNode(nodes1, parent, aboveThis); juggler->moveNode(nodes2, group, 0); *newGroup = group; *newLastChild = nodes2.last(); } void KisNodeManager::createQuickGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; createQuickGroupImpl(juggler, "", &parent, &above); } void KisNodeManager::createQuickClippingGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Clipping Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; KisImageSP image = m_d->view->image(); createQuickGroupImpl(juggler, image->nextLayerName(i18nc("default name for a clipping group layer", "Clipping Group")), &parent, &above); KisPaintLayerSP maskLayer = new KisPaintLayer(image.data(), i18nc("default name for quick clip group mask layer", "Mask Layer"), OPACITY_OPAQUE_U8, image->colorSpace()); maskLayer->disableAlphaChannel(true); juggler->addNode(KisNodeList() << maskLayer, parent, above); } void KisNodeManager::quickUngroup() { KisNodeSP active = activeNode(); if (!active) return; KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; KUndo2MagicString actionName = kundo2_i18n("Quick Ungroup"); if (parent && dynamic_cast(active.data())) { KisNodeList nodes = active->childNodes(QStringList(), KoProperties()); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, active); juggler->removeNode(KisNodeList() << active); } else if (parent && parent->parent()) { KisNodeSP grandParent = parent->parent(); KisNodeList allChildNodes = parent->childNodes(QStringList(), KoProperties()); KisNodeList allSelectedNodes = selectedNodes(); const bool removeParent = KritaUtils::compareListsUnordered(allChildNodes, allSelectedNodes); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(allSelectedNodes, grandParent, parent); if (removeParent) { juggler->removeNode(KisNodeList() << parent); } } } void KisNodeManager::selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps) { KisImageSP image = m_d->view->image(); KisNodeList nodes = KisLayerUtils::findNodesWithProps(image->root(), props, true); KisNodeList selectedNodes = this->selectedNodes(); if (KritaUtils::compareListsUnordered(nodes, selectedNodes)) { nodes = KisLayerUtils::findNodesWithProps(image->root(), invertedProps, true); } if (!nodes.isEmpty()) { slotImageRequestNodeReselection(nodes.last(), nodes); } } void KisNodeManager::selectAllNodes() { KoProperties props; selectLayersImpl(props, props); } void KisNodeManager::selectVisibleNodes() { KoProperties props; props.setProperty("visible", true); KoProperties invertedProps; invertedProps.setProperty("visible", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectLockedNodes() { KoProperties props; props.setProperty("locked", true); KoProperties invertedProps; invertedProps.setProperty("locked", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectInvisibleNodes() { KoProperties props; props.setProperty("visible", false); KoProperties invertedProps; invertedProps.setProperty("visible", true); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectUnlockedNodes() { KoProperties props; props.setProperty("locked", false); KoProperties invertedProps; invertedProps.setProperty("locked", true); selectLayersImpl(props, invertedProps); } diff --git a/libs/ui/kis_node_manager.h b/libs/ui/kis_node_manager.h index dabe293cb2..29c80823d0 100644 --- a/libs/ui/kis_node_manager.h +++ b/libs/ui/kis_node_manager.h @@ -1,265 +1,260 @@ /* * Copyright (C) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_NODE_MANAGER #define KIS_NODE_MANAGER #include #include #include "kis_types.h" #include class KActionCollection; class KoCompositeOp; class KoColorSpace; class KUndo2MagicString; class KisFilterStrategy; class KisViewManager; class KisActionManager; class KisView; class KisNodeSelectionAdapter; class KisNodeInsertionAdapter; class KisNodeDisplayModeAdapter; class KisNodeJugglerCompressed; class KoProperties; /** * The node manager passes requests for new layers or masks on to the mask and layer * managers. */ class KRITAUI_EXPORT KisNodeManager : public QObject { Q_OBJECT public: KisNodeManager(KisViewManager * view); ~KisNodeManager() override; void setView(QPointerimageView); Q_SIGNALS: /// emitted whenever a node is selected. void sigNodeActivated(KisNodeSP node); /// emitted whenever a different layer is selected. void sigLayerActivated(KisLayerSP layer); /// for the layer box: this sets the current node in the layerbox /// without telling the node manager that the node is activated, /// preventing loops (I think...) void sigUiNeedChangeActiveNode(KisNodeSP node); void sigUiNeedChangeSelectedNodes(const KisNodeList &nodes); public: void setup(KActionCollection * collection, KisActionManager* actionManager); void updateGUI(); /// Convenience function to get the active layer or mask KisNodeSP activeNode(); /// convenience function to get the active layer. If a mask is /// active, it's parent layer is the active layer. KisLayerSP activeLayer(); /// Get the paint device the user wants to paint on now KisPaintDeviceSP activePaintDevice(); /** * @return the active color space used for composition, meaning the color space * of the active mask, or the color space of the parent of the active layer */ const KoColorSpace* activeColorSpace(); /** * Sets opacity for the node in a universal way (masks/layers) */ void setNodeOpacity(KisNodeSP node, qint32 opacity, bool finalChange); /** * Sets compositeOp for the node in a universal way (masks/layers) */ void setNodeCompositeOp(KisNodeSP node, const KoCompositeOp* compositeOp); KisNodeList selectedNodes(); KisNodeSelectionAdapter* nodeSelectionAdapter() const; KisNodeInsertionAdapter* nodeInsertionAdapter() const; KisNodeDisplayModeAdapter* nodeDisplayModeAdapter() const; static bool isNodeHidden(KisNodeSP node, bool isGlobalSelectionHidden); public Q_SLOTS: /** * Explicitly activates \p node * The UI will be noticed that active node has been changed. * Both sigNodeActivated and sigUiNeedChangeActiveNode are emitted. * * WARNING: normally you needn't call this method manually. It is * automatically called when a node is added to the graph. If you * have some special cases when you need to activate a node, consider * adding them to KisDummiesFacadeBase instead. Calling this method * directly should be the last resort. * * \see slotUiActivatedNode for comparison */ void slotNonUiActivatedNode(KisNodeSP node); /** * Activates \p node. * All non-ui listeners are notified with sigNodeActivated, * sigUiNeedChangeActiveNode is *not* emitted. * * \see activateNode */ void slotUiActivatedNode(KisNodeSP node); /** * Adds a list of nodes without searching appropriate position for * it. You *must* ensure that the nodes are allowed to be added * to the parent, otherwise you'll get an assert. */ void addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis); /** * Moves a list of nodes without searching appropriate position * for it. You *must* ensure that the nodes are allowed to be * added to the parent, otherwise you'll get an assert. */ void moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis); /** * Copies a list of nodes without searching appropriate position * for it. You *must* ensure that the nodes are allowed to be * added to the parent, otherwise you'll get an assert. */ void copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis); /** * Create new layer from actually visible */ void createFromVisible(); void slotShowHideTimeline(bool value); void toggleIsolateActiveNode(); void toggleIsolateMode(bool checked); void slotUpdateIsolateModeAction(); void slotTryRestartIsolatedMode(); void moveNodeAt(KisNodeSP node, KisNodeSP parent, int index); void createNode(const QString& nodeType, bool quiet = false, KisPaintDeviceSP copyFrom = 0); void convertNode(const QString &nodeType); void nodesUpdated(); void nodeProperties(KisNodeSP node); void nodeOpacityChanged(qreal opacity, bool finalChange); void nodeCompositeOpChanged(const KoCompositeOp* op); void duplicateActiveNode(); void removeNode(); void mirrorNodeX(); void mirrorNodeY(); - void mirrorNode(KisNodeSP node, const KUndo2MagicString& commandName, Qt::Orientation orientation); + void mirrorAllNodesX(); + void mirrorAllNodesY(); + + + void mirrorNode(KisNodeSP node, const KUndo2MagicString& commandName, Qt::Orientation orientation, KisSelectionSP selection); + + void activateNextNode(); void activatePreviousNode(); void switchToPreviouslyActiveNode(); /** * move the active node up the nodestack. */ void raiseNode(); /** * move the active node down the nodestack */ void lowerNode(); - void rotate(double radians); - void rotate180(); - void rotateLeft90(); - void rotateRight90(); - void saveNodeAsImage(); void saveVectorLayerAsImage(); void slotSplitAlphaIntoMask(); void slotSplitAlphaWrite(); void slotSplitAlphaSaveMerged(); void toggleLock(); void toggleVisibility(); void toggleAlphaLock(); void toggleInheritAlpha(); /** * @brief slotSetSelectedNodes set the list of nodes selected in the layerbox. Selected nodes are not necessarily active nodes. * @param nodes the selected nodes */ void slotSetSelectedNodes(const KisNodeList &nodes); void slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes); void cutLayersToClipboard(); void copyLayersToClipboard(); void pasteLayersFromClipboard(); void createQuickGroup(); void createQuickClippingGroup(); void quickUngroup(); void selectAllNodes(); void selectVisibleNodes(); void selectLockedNodes(); void selectInvisibleNodes(); void selectUnlockedNodes(); public: - - - void shear(double angleX, double angleY); - - void scale(double sx, double sy, KisFilterStrategy *filterStrategy); - void removeSingleNode(KisNodeSP node); KisLayerSP createPaintLayer(); private: /** * Scales opacity from the range 0...1 * to the integer range 0...255 */ qint32 convertOpacityToInt(qreal opacity); void removeSelectedNodes(KisNodeList selectedNodes); void slotSomethingActivatedNodeImpl(KisNodeSP node); void createQuickGroupImpl(KisNodeJugglerCompressed *juggler, const QString &overrideGroupName, KisNodeSP *newGroup, KisNodeSP *newLastChild); void selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps); struct Private; Private * const m_d; }; #endif diff --git a/libs/ui/kis_painting_assistants_decoration.cpp b/libs/ui/kis_painting_assistants_decoration.cpp index 592222ac47..de5fe80866 100644 --- a/libs/ui/kis_painting_assistants_decoration.cpp +++ b/libs/ui/kis_painting_assistants_decoration.cpp @@ -1,483 +1,484 @@ /* * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2017 Scott Petrovic * * 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_painting_assistants_decoration.h" #include #include #include #include #include #include #include "kis_debug.h" #include "KisDocument.h" #include "kis_canvas2.h" #include "kis_icon_utils.h" #include "KisViewManager.h" #include #include struct KisPaintingAssistantsDecoration::Private { Private() : assistantVisible(false) , outlineVisible(false) , snapOnlyOneAssistant(true) , firstAssistant(0) , aFirstStroke(false) , m_handleSize(14) {} bool assistantVisible; bool outlineVisible; bool snapOnlyOneAssistant; KisPaintingAssistantSP firstAssistant; KisPaintingAssistantSP selectedAssistant; bool aFirstStroke; bool m_isEditingAssistants = false; bool m_outlineVisible = false; int m_handleSize; // size of editor handles on assistants // move, visibility, delete icons for each assistant. These only display while the assistant tool is active // these icons will be covered by the kis_paintint_assistant_decoration with things like the perspective assistant AssistantEditorData toolData; QPixmap m_iconDelete = KisIconUtils::loadIcon("dialog-cancel").pixmap(toolData.deleteIconSize, toolData.deleteIconSize); QPixmap m_iconSnapOn = KisIconUtils::loadIcon("visible").pixmap(toolData.snapIconSize, toolData.snapIconSize); QPixmap m_iconSnapOff = KisIconUtils::loadIcon("novisible").pixmap(toolData.snapIconSize, toolData.snapIconSize); QPixmap m_iconMove = KisIconUtils::loadIcon("transform-move").pixmap(toolData.moveIconSize, toolData.moveIconSize); KisCanvas2 * m_canvas = 0; }; KisPaintingAssistantsDecoration::KisPaintingAssistantsDecoration(QPointer parent) : KisCanvasDecoration("paintingAssistantsDecoration", parent), d(new Private) { setAssistantVisible(true); setOutlineVisible(true); + setPriority(95); d->snapOnlyOneAssistant = true; //turn on by default. } KisPaintingAssistantsDecoration::~KisPaintingAssistantsDecoration() { delete d; } void KisPaintingAssistantsDecoration::addAssistant(KisPaintingAssistantSP assistant) { QList assistants = view()->document()->assistants(); if (assistants.contains(assistant)) return; assistants.append(assistant); assistant->setAssistantGlobalColorCache(view()->document()->assistantsGlobalColor()); view()->document()->setAssistants(assistants); setVisible(!assistants.isEmpty()); emit assistantChanged(); } void KisPaintingAssistantsDecoration::removeAssistant(KisPaintingAssistantSP assistant) { QList assistants = view()->document()->assistants(); KIS_ASSERT_RECOVER_NOOP(assistants.contains(assistant)); if (assistants.removeAll(assistant)) { view()->document()->setAssistants(assistants); setVisible(!assistants.isEmpty()); emit assistantChanged(); } } void KisPaintingAssistantsDecoration::removeAll() { QList assistants = view()->document()->assistants(); assistants.clear(); view()->document()->setAssistants(assistants); setVisible(!assistants.isEmpty()); emit assistantChanged(); } QPointF KisPaintingAssistantsDecoration::adjustPosition(const QPointF& point, const QPointF& strokeBegin) { if (assistants().empty()) { return point; } if (assistants().count() == 1) { if(assistants().first()->isSnappingActive() == true){ QPointF newpoint = assistants().first()->adjustPosition(point, strokeBegin); // check for NaN if (newpoint.x() != newpoint.x()) return point; return newpoint; } } QPointF best = point; double distance = DBL_MAX; //the following tries to find the closest point to stroke-begin. It checks all assistants for the closest point// if(!d->snapOnlyOneAssistant){ Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { if(assistant->isSnappingActive() == true){//this checks if the assistant in question has it's snapping boolean turned on// QPointF pt = assistant->adjustPosition(point, strokeBegin); if (pt.x() != pt.x()) continue; double dist = qAbs(pt.x() - point.x()) + qAbs(pt.y() - point.y()); if (dist < distance) { best = pt; distance = dist; } } } } else if (d->aFirstStroke==false) { Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { if(assistant->isSnappingActive() == true){//this checks if the assistant in question has it's snapping boolean turned on// QPointF pt = assistant->adjustPosition(point, strokeBegin); if (pt.x() != pt.x()) continue; double dist = qAbs(pt.x() - point.x()) + qAbs(pt.y() - point.y()); if (dist < distance) { best = pt; distance = dist; d->firstAssistant = assistant; } } } } else if(d->firstAssistant) { //make sure there's a first assistant to begin with.// best = d->firstAssistant->adjustPosition(point, strokeBegin); } else { d->aFirstStroke=false; } //this is here to be compatible with the movement in the perspective tool. qreal dx = point.x() - strokeBegin.x(), dy = point.y() - strokeBegin.y(); if (dx * dx + dy * dy >= 4.0) { // allow some movement before snapping d->aFirstStroke=true; } return best; } void KisPaintingAssistantsDecoration::endStroke() { d->aFirstStroke = false; Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { assistant->endStroke(); } } void KisPaintingAssistantsDecoration::drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter,KisCanvas2* canvas) { if(assistants().length() == 0) { return; // no assistants to worry about, ok to exit } if (!canvas) { dbgFile<<"canvas does not exist in painting assistant decoration, you may have passed arguments incorrectly:"<m_canvas = canvas; } // the preview functionality for assistants. do not show while editing if (d->m_isEditingAssistants) { d->m_outlineVisible = false; } else { d->m_outlineVisible = outlineVisibility(); } Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { assistant->drawAssistant(gc, updateRect, converter, true, canvas, assistantVisibility(), d->m_outlineVisible); if (isEditingAssistants()) { drawHandles(assistant, gc, converter); } } // draw editor controls on top of all assistant lines (why this code is last) if (isEditingAssistants()) { Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { drawEditorWidget(assistant, gc, converter); } } } void KisPaintingAssistantsDecoration::drawHandles(KisPaintingAssistantSP assistant, QPainter& gc, const KisCoordinatesConverter *converter) { QTransform initialTransform = converter->documentToWidgetTransform(); QColor colorToPaint = assistant->effectiveAssistantColor(); Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) { QPointF transformedHandle = initialTransform.map(*handle); QRectF ellipse(transformedHandle - QPointF(handleSize() * 0.5, handleSize() * 0.5), QSizeF(handleSize(), handleSize())); QPainterPath path; path.addEllipse(ellipse); gc.save(); gc.setPen(Qt::NoPen); gc.setBrush(colorToPaint); gc.drawPath(path); gc.restore(); } // some assistants have side handles like the vanishing point assistant Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->sideHandles()) { QPointF transformedHandle = initialTransform.map(*handle); QRectF ellipse(transformedHandle - QPointF(handleSize() * 0.5, handleSize() * 0.5), QSizeF(handleSize(), handleSize())); QPainterPath path; path.addEllipse(ellipse); gc.save(); gc.setPen(Qt::NoPen); gc.setBrush(colorToPaint); gc.drawPath(path); gc.restore(); } } int KisPaintingAssistantsDecoration::handleSize() { return d->m_handleSize; } void KisPaintingAssistantsDecoration::setHandleSize(int handleSize) { d->m_handleSize = handleSize; } QList KisPaintingAssistantsDecoration::handles() { QList hs; Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) { if (!hs.contains(handle)) { hs.push_back(handle); } } Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->sideHandles()) { if (!hs.contains(handle)) { hs.push_back(handle); } } } return hs; } QList KisPaintingAssistantsDecoration::assistants() const { QList assistants = view()->document()->assistants(); return assistants; } KisPaintingAssistantSP KisPaintingAssistantsDecoration::selectedAssistant() { return d->selectedAssistant; } void KisPaintingAssistantsDecoration::setSelectedAssistant(KisPaintingAssistantSP assistant) { d->selectedAssistant = assistant; emit selectedAssistantChanged(); } void KisPaintingAssistantsDecoration::deselectAssistant() { d->selectedAssistant.clear(); } void KisPaintingAssistantsDecoration::setAssistantVisible(bool set) { d->assistantVisible=set; } void KisPaintingAssistantsDecoration::setOutlineVisible(bool set) { d->outlineVisible=set; } void KisPaintingAssistantsDecoration::setOnlyOneAssistantSnap(bool assistant) { d->snapOnlyOneAssistant = assistant; } bool KisPaintingAssistantsDecoration::assistantVisibility() { return d->assistantVisible; } bool KisPaintingAssistantsDecoration::outlineVisibility() { return d->outlineVisible; } void KisPaintingAssistantsDecoration::uncache() { Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { assistant->uncache(); } } void KisPaintingAssistantsDecoration::toggleAssistantVisible() { setAssistantVisible(!assistantVisibility()); uncache(); } void KisPaintingAssistantsDecoration::toggleOutlineVisible() { setOutlineVisible(!outlineVisibility()); } QColor KisPaintingAssistantsDecoration::globalAssistantsColor() { return view()->document()->assistantsGlobalColor(); } void KisPaintingAssistantsDecoration::setGlobalAssistantsColor(QColor color) { // view()->document() is referenced multiple times in this class // it is used to later store things in the KRA file when saving. view()->document()->setAssistantsGlobalColor(color); Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) { assistant->setAssistantGlobalColorCache(color); } uncache(); } void KisPaintingAssistantsDecoration::activateAssistantsEditor() { setVisible(true); // this turns on the decorations in general. we leave it on at this point d->m_isEditingAssistants = true; uncache(); // updates visuals when editing } void KisPaintingAssistantsDecoration::deactivateAssistantsEditor() { if (!d->m_canvas) { return; } d->m_isEditingAssistants = false; // some elements are hidden when we aren't editing uncache(); // updates visuals when not editing } bool KisPaintingAssistantsDecoration::isEditingAssistants() { return d->m_isEditingAssistants; } QPointF KisPaintingAssistantsDecoration::snapToGuide(KoPointerEvent *e, const QPointF &offset, bool useModifiers) { if (!d->m_canvas || !d->m_canvas->currentImage()) { return e->point; } KoSnapGuide *snapGuide = d->m_canvas->snapGuide(); QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier); return pos; } QPointF KisPaintingAssistantsDecoration::snapToGuide(const QPointF& pt, const QPointF &offset) { if (!d->m_canvas) { return pt; } KoSnapGuide *snapGuide = d->m_canvas->snapGuide(); QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier); return pos; } /* * functions only used internally in this class * we potentially could make some of these inline to speed up performance */ void KisPaintingAssistantsDecoration::drawEditorWidget(KisPaintingAssistantSP assistant, QPainter& gc, const KisCoordinatesConverter *converter) { if (!assistant->isAssistantComplete()) { return; } QTransform initialTransform = converter->documentToWidgetTransform(); // We are going to put all of the assistant actions below the bounds of the assistant // so they are out of the way // assistant->buttonPosition() gets the center X/Y position point QPointF actionsPosition = initialTransform.map(assistant->buttonPosition()); AssistantEditorData toolData; // shared const data for positioning and sizing QPointF iconMovePosition(actionsPosition + toolData.moveIconPosition); QPointF iconSnapPosition(actionsPosition + toolData.snapIconPosition); QPointF iconDeletePosition(actionsPosition + toolData.deleteIconPosition); // Background container for helpers QBrush backgroundColor = d->m_canvas->viewManager()->mainWindow()->palette().window(); QPointF actionsBGRectangle(actionsPosition + QPointF(10, 10)); gc.setRenderHint(QPainter::Antialiasing); QPainterPath bgPath; bgPath.addRoundedRect(QRectF(actionsBGRectangle.x(), actionsBGRectangle.y(), 110, 40), 6, 6); QPen stroke(QColor(60, 60, 60, 80), 2); // if the assistant is selected, make outline stroke fatter and use theme's highlight color // for better visual feedback if (selectedAssistant()) { // there might not be a selected assistant, so do not seg fault if (assistant->buttonPosition() == selectedAssistant()->buttonPosition()) { stroke.setWidth(4); stroke.setColor(qApp->palette().color(QPalette::Highlight)); } } // draw the final result gc.setPen(stroke); gc.fillPath(bgPath, backgroundColor); gc.drawPath(bgPath); // Move Assistant Tool helper gc.drawPixmap(iconMovePosition, d->m_iconMove); // active toggle if (assistant->isSnappingActive() == true) { gc.drawPixmap(iconSnapPosition, d->m_iconSnapOn); } else { gc.drawPixmap(iconSnapPosition, d->m_iconSnapOff); } gc.drawPixmap(iconDeletePosition, d->m_iconDelete); } diff --git a/libs/ui/kis_png_converter.cpp b/libs/ui/kis_png_converter.cpp index 2bf6520a8a..f41e5571c0 100644 --- a/libs/ui/kis_png_converter.cpp +++ b/libs/ui/kis_png_converter.cpp @@ -1,1317 +1,1317 @@ /* * 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 #include "dialogs/kis_dlg_png_import.h" #include "kis_clipboard.h" #include #include "kis_undo_stores.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); dbgFile << "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); dbgFile << "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); dbgFile << "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(true); quint32 behaviour = cfg.pasteBehaviour(); if (behaviour == PASTE_ASK) { KisDlgPngImport dlg(m_path, csName.first, csName.second); KisCursorOverrideHijacker hijacker; Q_UNUSED(hijacker); dlg.exec(); if (!dlg.profile().isEmpty()) { profile = KoColorSpaceRegistry::instance()->profileByName(dlg.profile()); } } } dbgFile << "no embedded profile, will use the default profile"; } const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second); // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { 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 if (csName.first == GrayAColorModelID.id()) { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "Gray-D50-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 KisImageSP if (m_image == 0) { KisUndoStore *store = m_doc ? m_doc->createUndoStore() : new KisSurrogateUndoStore(); m_image = new KisImage(store, width, height, cs, "built 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: " << text_ptr[i].key << ", containing: " << text_ptr[i].text << ": " << (key == "raw profile type exif " ? "isExif" : "something else"); 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") { info->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 QScopedPointer reader; try { if (interlace_type == PNG_INTERLACE_ADAM7) { reader.reset(new KisPNGReaderFullImage(png_ptr, info_ptr, width, height)); } else { reader.reset(new KisPNGReaderLineByLine(png_ptr, info_ptr, width, height)); } } catch (const 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); 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 (device->colorSpace()->colorDepthId() == Float16BitsColorDepthID || device->colorSpace()->colorDepthId() == Float32BitsColorDepthID || device->colorSpace()->colorDepthId() == Float64BitsColorDepthID) { const KoColorSpace *dstcs = KoColorSpaceRegistry::instance()->colorSpace(device->colorSpace()->colorModelId().id(), Integer16BitsColorDepthID.id(), device->colorSpace()->profile()); KisPaintDeviceSP tmp = new KisPaintDevice(dstcs); 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); KUndo2Command *cmd = device->convertTo(cs); delete cmd; } // 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 QScopedArrayPointer palette; 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.reset(new png_color[255]); KisSequentialIterator it(device, imageRect); bool toomuchcolor = false; while (it.nextPixel()) { 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++; } } 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 { palette.reset(); } } 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); }*/ // we should ensure we don't access non-existing palette object KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(palette || color_type != PNG_COLOR_TYPE_PALETTE, KisImageBuilder_RESULT_FAILURE); // set the palette if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_PLTE(png_ptr, info_ptr, palette.data(), 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, (png_const_charp)"icc", PNG_COMPRESSION_TYPE_BASE, (png_const_bytep)colorProfileData.constData(), colorProfileData . size()); #else // older version of libpng has a problem with constness on the parameters char typeString[] = "icc"; png_set_iCCP(png_ptr, info_ptr, typeString, PNG_COMPRESSION_TYPE_BASE, colorProfileData.data(), colorProfileData . size()); #endif } // save comments from the document information // warning: according to the official png spec, the keys need to be capitalized! if (m_doc) { png_text texts[4]; int nbtexts = 0; KoDocumentInfo * info = m_doc->documentInfo(); QString title = info->aboutInfo("title"); if (!title.isEmpty() && options.storeMetaData) { fillText(texts + nbtexts, "Title", title); nbtexts++; } QString abstract = info->aboutInfo("subject"); if (abstract.isEmpty()) { abstract = info->aboutInfo("abstract"); } if (!abstract.isEmpty() && options.storeMetaData) { QString keywords = info->aboutInfo("keyword"); if (!keywords.isEmpty()) { abstract = abstract + " keywords: " + keywords; } fillText(texts + nbtexts, "Description", abstract); nbtexts++; } QString license = info->aboutInfo("license"); if (!license.isEmpty() && options.storeMetaData) { fillText(texts + nbtexts, "Copyright", license); nbtexts++; } QString author = info->authorInfo("creator"); if (!author.isEmpty() && options.storeAuthor) { if (!info->authorContactInfo().isEmpty()) { QString contact = info->authorContactInfo().at(0); if (!contact.isEmpty()) { author = author+"("+contact+")"; } } 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); struct RowPointersStruct { RowPointersStruct(const QSize &size, int pixelSize) : numRows(size.height()) { rows = new png_byte*[numRows]; for (int i = 0; i < numRows; i++) { rows[i] = new png_byte[size.width() * pixelSize]; } } ~RowPointersStruct() { for (int i = 0; i < numRows; i++) { delete[] rows[i]; } delete[] rows; } const int numRows = 0; png_byte** rows = 0; }; // Fill the data structure RowPointersStruct rowPointers(imageRect.size(), device->pixelSize()); int row = 0; for (int y = imageRect.y(); y < imageRect.y() + imageRect.height(); y++, row++) { KisHLineConstIteratorSP it = device->createHLineConstIteratorNG(imageRect.x(), y, imageRect.width()); switch (color_type) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: if (color_nb_bits == 16) { quint16 *dst = reinterpret_cast(rowPointers.rows[row]); do { const quint16 *d = reinterpret_cast(it->oldRawData()); *(dst++) = d[0]; if (options.alpha) *(dst++) = d[1]; } while (it->nextPixel()); } else { quint8 *dst = rowPointers.rows[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(rowPointers.rows[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 = rowPointers.rows[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 = rowPointers.rows[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: return KisImageBuilder_RESULT_UNSUPPORTED; } } png_write_image(png_ptr, rowPointers.rows); // Writing is over png_write_end(png_ptr, info_ptr); // Free memory png_destroy_write_struct(&png_ptr, &info_ptr); 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_selection_decoration.cc b/libs/ui/kis_selection_decoration.cc index db7d67aa47..deecb1a39d 100644 --- a/libs/ui/kis_selection_decoration.cc +++ b/libs/ui/kis_selection_decoration.cc @@ -1,225 +1,228 @@ /* * Copyright (c) 2008 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 "kis_selection_decoration.h" #include #include #include #include #include "kis_types.h" #include "KisViewManager.h" #include "kis_selection.h" #include "kis_image.h" #include "flake/kis_shape_selection.h" #include "kis_pixel_selection.h" #include "kis_update_outline_job.h" #include "kis_selection_manager.h" #include "canvas/kis_canvas2.h" #include "kis_canvas_resource_provider.h" #include "kis_coordinates_converter.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_image_config.h" #include "KisImageConfigNotifier.h" #include "kis_painting_tweaks.h" #include "KisView.h" #include "kis_selection_mask.h" #include static const unsigned int ANT_LENGTH = 4; static const unsigned int ANT_SPACE = 4; static const unsigned int ANT_ADVANCE_WIDTH = ANT_LENGTH + ANT_SPACE; KisSelectionDecoration::KisSelectionDecoration(QPointerview) : KisCanvasDecoration("selection", view), m_signalCompressor(500 /*ms*/, KisSignalCompressor::FIRST_INACTIVE), m_offset(0), m_mode(Ants) { KisPaintingTweaks::initAntsPen(&m_antsPen, &m_outlinePen, ANT_LENGTH, ANT_SPACE); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); slotConfigChanged(); m_antsTimer = new QTimer(this); m_antsTimer->setInterval(150); m_antsTimer->setSingleShot(false); connect(m_antsTimer, SIGNAL(timeout()), SLOT(antsAttackEvent())); connect(&m_signalCompressor, SIGNAL(timeout()), SLOT(slotStartUpdateSelection())); + + // selections should be at the top of the stack + setPriority(100); } KisSelectionDecoration::~KisSelectionDecoration() { } KisSelectionDecoration::Mode KisSelectionDecoration::mode() const { return m_mode; } void KisSelectionDecoration::setMode(Mode mode) { m_mode = mode; selectionChanged(); } bool KisSelectionDecoration::selectionIsActive() { KisImageWSP image = view()->image(); Q_ASSERT(image); Q_UNUSED(image); KisSelectionSP selection = view()->selection(); return visible() && selection && (selection->hasPixelSelection() || selection->hasShapeSelection()) && selection->isVisible(); } void KisSelectionDecoration::selectionChanged() { KisSelectionMaskSP mask = qobject_cast(view()->currentNode().data()); if (!mask || !mask->active() || !mask->visible(true)) { mask = 0; } if (!view()->isCurrent() || view()->viewManager()->mainWindow() == KisPart::instance()->currentMainwindow()) { view()->image()->setOverlaySelectionMask(mask); } KisSelectionSP selection = view()->selection(); if (!mask && selection && selectionIsActive()) { if ((m_mode == Ants && selection->outlineCacheValid()) || (m_mode == Mask && selection->thumbnailImageValid())) { m_signalCompressor.stop(); if (m_mode == Ants) { m_outlinePath = selection->outlineCache(); m_antsTimer->start(); } else { m_thumbnailImage = selection->thumbnailImage(); m_thumbnailImageTransform = selection->thumbnailImageTransform(); } if (view() && view()->canvasBase()) { view()->canvasBase()->updateCanvas(); } } else { m_signalCompressor.start(); } } else { m_signalCompressor.stop(); m_outlinePath = QPainterPath(); m_thumbnailImage = QImage(); m_thumbnailImageTransform = QTransform(); view()->canvasBase()->updateCanvas(); m_antsTimer->stop(); } } void KisSelectionDecoration::slotStartUpdateSelection() { KisSelectionSP selection = view()->selection(); if (!selection) return; view()->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, m_mode == Mask, m_maskColor)); } void KisSelectionDecoration::slotConfigChanged() { KisImageConfig imageConfig(true); KisConfig cfg(true); m_maskColor = imageConfig.selectionOverlayMaskColor(); m_antialiasSelectionOutline = cfg.antialiasSelectionOutline(); } void KisSelectionDecoration::antsAttackEvent() { KisSelectionSP selection = view()->selection(); if (!selection) return; if (selectionIsActive()) { m_offset = (m_offset + 1) % ANT_ADVANCE_WIDTH; m_antsPen.setDashOffset(m_offset); view()->canvasBase()->updateCanvas(); } } void KisSelectionDecoration::drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter, KisCanvas2 *canvas) { Q_UNUSED(updateRect); Q_UNUSED(canvas); if (!selectionIsActive()) return; if ((m_mode == Ants && m_outlinePath.isEmpty()) || (m_mode == Mask && m_thumbnailImage.isNull())) return; QTransform transform = converter->imageToWidgetTransform(); gc.save(); gc.setTransform(transform, false); if (m_mode == Mask) { gc.setRenderHints(QPainter::SmoothPixmapTransform | QPainter::HighQualityAntialiasing, false); gc.setTransform(m_thumbnailImageTransform, true); gc.drawImage(QPoint(), m_thumbnailImage); QRect r1 = m_thumbnailImageTransform.inverted().mapRect(view()->image()->bounds()); QRect r2 = m_thumbnailImage.rect(); QPainterPath p1; p1.addRect(r1); QPainterPath p2; p2.addRect(r2); gc.setBrush(m_maskColor); gc.setPen(Qt::NoPen); gc.drawPath(p1 - p2); } else /* if (m_mode == Ants) */ { gc.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing, m_antialiasSelectionOutline); // render selection outline in white gc.setPen(m_outlinePen); gc.drawPath(m_outlinePath); // render marching ants in black (above the white outline) gc.setPen(m_antsPen); gc.drawPath(m_outlinePath); } gc.restore(); } void KisSelectionDecoration::setVisible(bool v) { KisCanvasDecoration::setVisible(v); selectionChanged(); } diff --git a/libs/ui/kisexiv2/kis_exif_io.cpp b/libs/ui/kisexiv2/kis_exif_io.cpp index 5bce1725b9..1a01fedf33 100644 --- a/libs/ui/kisexiv2/kis_exif_io.cpp +++ b/libs/ui/kisexiv2/kis_exif_io.cpp @@ -1,639 +1,639 @@ /* * 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 "kis_exif_io.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_exiv2.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include struct KisExifIO::Private { }; // ---- Exception conversion functions ---- // // convert ExifVersion and FlashpixVersion to a KisMetaData value KisMetaData::Value exifVersionToKMDValue(const Exiv2::Value::AutoPtr value) { const Exiv2::DataValue* dvalue = dynamic_cast(&*value); if (dvalue) { Q_ASSERT(dvalue); QByteArray array(dvalue->count(), 0); dvalue->copy((Exiv2::byte*)array.data()); return KisMetaData::Value(QString(array)); } else { Q_ASSERT(value->typeId() == Exiv2::asciiString); return KisMetaData::Value(QString::fromLatin1(value->toString().c_str())); } } // convert from KisMetaData value to ExifVersion and FlashpixVersion Exiv2::Value* kmdValueToExifVersion(const KisMetaData::Value& value) { Exiv2::DataValue* dvalue = new Exiv2::DataValue; QString ver = value.asVariant().toString(); dvalue->read((const Exiv2::byte*)ver.toLatin1().constData(), ver.size()); return dvalue; } // Convert an exif array of integer string to a KisMetaData array of integer KisMetaData::Value exifArrayToKMDIntOrderedArray(const Exiv2::Value::AutoPtr value) { QList v; const Exiv2::DataValue* dvalue = dynamic_cast(&*value); if (dvalue) { QByteArray array(dvalue->count(), 0); dvalue->copy((Exiv2::byte*)array.data()); for (int i = 0; i < array.size(); i++) { QChar c((char)array[i]); v.push_back(KisMetaData::Value(QString(c).toInt(0))); } } else { Q_ASSERT(value->typeId() == Exiv2::asciiString); QString str = QString::fromLatin1(value->toString().c_str()); v.push_back(KisMetaData::Value(str.toInt())); } return KisMetaData::Value(v, KisMetaData::Value::OrderedArray); } // Convert a KisMetaData array of integer to an exif array of integer string Exiv2::Value* kmdIntOrderedArrayToExifArray(const KisMetaData::Value& value) { QList v = value.asArray(); QByteArray s; for (QList::iterator it = v.begin(); it != v.end(); ++it) { int val = it->asVariant().toInt(0); s += QByteArray::number(val); } return new Exiv2::DataValue((const Exiv2::byte*)s.data(), s.size()); } QDateTime exivValueToDateTime(const Exiv2::Value::AutoPtr value) { return QDateTime::fromString(value->toString().c_str(), Qt::ISODate); } template inline T fixEndianess(T v, Exiv2::ByteOrder order) { switch (order) { case Exiv2::invalidByteOrder: return v; case Exiv2::littleEndian: return qFromLittleEndian(v); case Exiv2::bigEndian: return qFromBigEndian(v); } warnKrita << "KisExifIO: unknown byte order"; return v; } Exiv2::ByteOrder invertByteOrder(Exiv2::ByteOrder order) { switch (order) { case Exiv2::littleEndian: return Exiv2::bigEndian; case Exiv2::bigEndian: return Exiv2::littleEndian; case Exiv2::invalidByteOrder: warnKrita << "KisExifIO: Can't invert Exiv2::invalidByteOrder"; return Exiv2::invalidByteOrder; } return Exiv2::invalidByteOrder; } KisMetaData::Value exifOECFToKMDOECFStructure(const Exiv2::Value::AutoPtr value, Exiv2::ByteOrder order) { QMap oecfStructure; const Exiv2::DataValue* dvalue = dynamic_cast(&*value); Q_ASSERT(dvalue); QByteArray array(dvalue->count(), 0); dvalue->copy((Exiv2::byte*)array.data()); int columns = fixEndianess((reinterpret_cast(array.data()))[0], order); int rows = fixEndianess((reinterpret_cast(array.data()))[1], order); if ((columns * rows + 4) > dvalue->count()) { // Sometime byteOrder get messed up (especially if metadata got saved with kexiv2 library, or any library that doesn't save back with the same byte order as the camera) order = invertByteOrder(order); columns = fixEndianess((reinterpret_cast(array.data()))[0], order); rows = fixEndianess((reinterpret_cast(array.data()))[1], order); Q_ASSERT((columns * rows + 4) > dvalue->count()); } oecfStructure["Columns"] = KisMetaData::Value(columns); oecfStructure["Rows"] = KisMetaData::Value(rows); int index = 4; QList names; for (int i = 0; i < columns; i++) { int lastIndex = array.indexOf((char)0, index); QString name = array.mid(index, lastIndex - index); if (index != lastIndex) { index = lastIndex + 1; dbgMetaData << "Name [" << i << "] =" << name; names.append(KisMetaData::Value(name)); } else { names.append(KisMetaData::Value("")); } } oecfStructure["Names"] = KisMetaData::Value(names, KisMetaData::Value::OrderedArray); QList values; qint32* dataIt = reinterpret_cast(array.data() + index); for (int i = 0; i < columns; i++) { for (int j = 0; j < rows; j++) { values.append(KisMetaData::Value(KisMetaData::Rational(fixEndianess(dataIt[0], order), fixEndianess(dataIt[1], order)))); dataIt += 2; } } oecfStructure["Values"] = KisMetaData::Value(values, KisMetaData::Value::OrderedArray); dbgMetaData << "OECF: " << ppVar(columns) << ppVar(rows) << ppVar(dvalue->count()); return KisMetaData::Value(oecfStructure); } Exiv2::Value* kmdOECFStructureToExifOECF(const KisMetaData::Value& value) { QMap oecfStructure = value.asStructure(); quint16 columns = oecfStructure["Columns"].asVariant().toInt(0); quint16 rows = oecfStructure["Rows"].asVariant().toInt(0); QList names = oecfStructure["Names"].asArray(); QList values = oecfStructure["Values"].asArray(); Q_ASSERT(columns*rows == values.size()); int length = 4 + rows * columns * 8; // The 4 byte for storing rows/columns and the rows*columns*sizeof(rational) bool saveNames = (!names.empty() && names[0].asVariant().toString().size() > 0); if (saveNames) { for (int i = 0; i < columns; i++) { length += names[i].asVariant().toString().size() + 1; } } QByteArray array(length, 0); (reinterpret_cast(array.data()))[0] = columns; (reinterpret_cast(array.data()))[1] = rows; int index = 4; if (saveNames) { for (int i = 0; i < columns; i++) { QByteArray name = names[i].asVariant().toString().toLatin1(); name.append((char)0); memcpy(array.data() + index, name.data(), name.size()); index += name.size(); } } qint32* dataIt = reinterpret_cast(array.data() + index); for (QList::iterator it = values.begin(); it != values.end(); ++it) { dataIt[0] = it->asRational().numerator; dataIt[1] = it->asRational().denominator; dataIt += 2; } return new Exiv2::DataValue((const Exiv2::byte*)array.data(), array.size()); } KisMetaData::Value deviceSettingDescriptionExifToKMD(const Exiv2::Value::AutoPtr value) { QMap deviceSettingStructure; QByteArray array; const Exiv2::DataValue* dvalue = dynamic_cast(&*value); if(dvalue) { array.resize(dvalue->count()); dvalue->copy((Exiv2::byte*)array.data()); } else { Q_ASSERT(value->typeId() == Exiv2::unsignedShort); array.resize(2 * value->count()); value->copy((Exiv2::byte*)array.data(), Exiv2::littleEndian); } int columns = (reinterpret_cast(array.data()))[0]; int rows = (reinterpret_cast(array.data()))[1]; deviceSettingStructure["Columns"] = KisMetaData::Value(columns); deviceSettingStructure["Rows"] = KisMetaData::Value(rows); QList settings; QByteArray null(2, 0); for (int index = 4; index < array.size(); ) { const int lastIndex = array.indexOf(null, index); const int numChars = (lastIndex - index) / 2; // including trailing zero QString setting = QString::fromUtf16((ushort*)(void*)( array.data() + index), numChars); index = lastIndex + 2; dbgMetaData << "Setting << " << setting; settings.append(KisMetaData::Value(setting)); } deviceSettingStructure["Settings"] = KisMetaData::Value(settings, KisMetaData::Value::OrderedArray); return KisMetaData::Value(deviceSettingStructure); } Exiv2::Value* deviceSettingDescriptionKMDToExif(const KisMetaData::Value& value) { QMap deviceSettingStructure = value.asStructure(); quint16 columns = deviceSettingStructure["Columns"].asVariant().toInt(0); quint16 rows = deviceSettingStructure["Rows"].asVariant().toInt(0); QTextCodec* codec = QTextCodec::codecForName("UTF-16"); QList settings = deviceSettingStructure["Settings"].asArray(); QByteArray array(4, 0); (reinterpret_cast(array.data()))[0] = columns; (reinterpret_cast(array.data()))[1] = rows; for (int i = 0; i < settings.count(); i++) { QString str = settings[i].asVariant().toString(); QByteArray setting = codec->fromUnicode(str); array.append(setting); } return new Exiv2::DataValue((const Exiv2::byte*)array.data(), array.size()); } KisMetaData::Value cfaPatternExifToKMD(const Exiv2::Value::AutoPtr value, Exiv2::ByteOrder order) { QMap cfaPatternStructure; const Exiv2::DataValue* dvalue = dynamic_cast(&*value); Q_ASSERT(dvalue); QByteArray array(dvalue->count(), 0); dvalue->copy((Exiv2::byte*)array.data()); int columns = fixEndianess((reinterpret_cast(array.data()))[0], order); int rows = fixEndianess((reinterpret_cast(array.data()))[1], order); if ((columns * rows + 4) != dvalue->count()) { // Sometime byteOrder get messed up (especially if metadata got saved with kexiv2 library, or any library that doesn't save back with the same byte order as the camera) order = invertByteOrder(order); columns = fixEndianess((reinterpret_cast(array.data()))[0], order); rows = fixEndianess((reinterpret_cast(array.data()))[1], order); Q_ASSERT((columns * rows + 4) == dvalue->count()); } cfaPatternStructure["Columns"] = KisMetaData::Value(columns); cfaPatternStructure["Rows"] = KisMetaData::Value(rows); QList values; int index = 4; for (int i = 0; i < columns * rows; i++) { values.append(KisMetaData::Value(*(array.data() + index))); index++; } cfaPatternStructure["Values"] = KisMetaData::Value(values, KisMetaData::Value::OrderedArray); dbgMetaData << "CFAPattern " << ppVar(columns) << " " << ppVar(rows) << ppVar(values.size()) << ppVar(dvalue->count()); return KisMetaData::Value(cfaPatternStructure); } Exiv2::Value* cfaPatternKMDToExif(const KisMetaData::Value& value) { QMap cfaStructure = value.asStructure(); quint16 columns = cfaStructure["Columns"].asVariant().toInt(0); quint16 rows = cfaStructure["Rows"].asVariant().toInt(0); QList values = cfaStructure["Values"].asArray(); Q_ASSERT(columns*rows == values.size()); QByteArray array(4 + columns*rows, 0); (reinterpret_cast(array.data()))[0] = columns; (reinterpret_cast(array.data()))[1] = rows; for (int i = 0; i < columns * rows; i++) { quint8 val = values[i].asVariant().toUInt(); *(array.data() + 4 + i) = val; } dbgMetaData << "Cfa Array " << ppVar(columns) << ppVar(rows) << ppVar(array.size()); return new Exiv2::DataValue((const Exiv2::byte*)array.data(), array.size()); } // Read and write Flash // KisMetaData::Value flashExifToKMD(const Exiv2::Value::AutoPtr value) { uint16_t v = value->toLong(); QMap flashStructure; bool fired = (v & 0x01); // bit 1 is whether flash was fired or not flashStructure["Fired"] = QVariant(fired); int ret = ((v >> 1) & 0x03); // bit 2 and 3 are Return flashStructure["Return"] = QVariant(ret); int mode = ((v >> 3) & 0x03); // bit 4 and 5 are Mode flashStructure["Mode"] = QVariant(mode); bool function = ((v >> 5) & 0x01); // bit 6 if function flashStructure["Function"] = QVariant(function); bool redEye = ((v >> 6) & 0x01); // bit 7 if function flashStructure["RedEyeMode"] = QVariant(redEye); return KisMetaData::Value(flashStructure); } Exiv2::Value* flashKMDToExif(const KisMetaData::Value& value) { uint16_t v = 0; QMap flashStructure = value.asStructure(); v = flashStructure["Fired"].asVariant().toBool(); v |= ((flashStructure["Return"].asVariant().toInt() & 0x03) << 1); v |= ((flashStructure["Mode"].asVariant().toInt() & 0x03) << 3); v |= ((flashStructure["Function"].asVariant().toInt() & 0x03) << 5); v |= ((flashStructure["RedEyeMode"].asVariant().toInt() & 0x03) << 6); return new Exiv2::ValueType(v); } // ---- Implementation of KisExifIO ----// KisExifIO::KisExifIO() : d(new Private) { } KisExifIO::~KisExifIO() { delete d; } bool KisExifIO::saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType headerType) const { ioDevice->open(QIODevice::WriteOnly); Exiv2::ExifData exifData; if (headerType == KisMetaData::IOBackend::JpegHeader) { QByteArray header(6, 0); header[0] = 0x45; header[1] = 0x78; header[2] = 0x69; header[3] = 0x66; header[4] = 0x00; header[5] = 0x00; ioDevice->write(header); } for (QHash::const_iterator it = store->begin(); it != store->end(); ++it) { try { const KisMetaData::Entry& entry = *it; dbgMetaData << "Trying to save: " << entry.name() << " of " << entry.schema()->prefix() << ":" << entry.schema()->uri(); QString exivKey; if (entry.schema()->uri() == KisMetaData::Schema::TIFFSchemaUri) { exivKey = "Exif.Image." + entry.name(); } else if (entry.schema()->uri() == KisMetaData::Schema::EXIFSchemaUri) { // Distinguish between exif and gps if (entry.name().left(3) == "GPS") { exivKey = "Exif.GPS." + entry.name(); } else { exivKey = "Exif.Photo." + entry.name(); } } else if (entry.schema()->uri() == KisMetaData::Schema::DublinCoreSchemaUri) { if (entry.name() == "description") { exivKey = "Exif.Image.ImageDescription"; } else if (entry.name() == "creator") { exivKey = "Exif.Image.Artist"; } else if (entry.name() == "rights") { exivKey = "Exif.Image.Copyright"; } } else if (entry.schema()->uri() == KisMetaData::Schema::XMPSchemaUri) { if (entry.name() == "ModifyDate") { exivKey = "Exif.Image.DateTime"; } else if (entry.name() == "CreatorTool") { exivKey = "Exif.Image.Software"; } } else if (entry.schema()->uri() == KisMetaData::Schema::MakerNoteSchemaUri) { if (entry.name() == "RawData") { exivKey = "Exif.Photo.MakerNote"; } } dbgMetaData << "Saving " << entry.name() << " to " << exivKey; if (exivKey.isEmpty()) { dbgMetaData << entry.qualifiedName() << " is unsavable to EXIF"; } else { Exiv2::ExifKey exifKey(qPrintable(exivKey)); Exiv2::Value* v = 0; if (exivKey == "Exif.Photo.ExifVersion" || exivKey == "Exif.Photo.FlashpixVersion") { v = kmdValueToExifVersion(entry.value()); } else if (exivKey == "Exif.Photo.FileSource") { char s[] = { 0x03 }; v = new Exiv2::DataValue((const Exiv2::byte*)s, 1); } else if (exivKey == "Exif.Photo.SceneType") { char s[] = { 0x01 }; v = new Exiv2::DataValue((const Exiv2::byte*)s, 1); } else if (exivKey == "Exif.Photo.ComponentsConfiguration") { v = kmdIntOrderedArrayToExifArray(entry.value()); } else if (exivKey == "Exif.Image.Artist") { // load as dc:creator KisMetaData::Value creator = entry.value(); if (entry.value().asArray().size() > 0) { creator = entry.value().asArray()[0]; } #if EXIV2_MAJOR_VERSION == 0 && EXIV2_MINOR_VERSION <= 20 v = kmdValueToExivValue(creator, Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId())); #else v = kmdValueToExivValue(creator, exifKey.defaultTypeId()); #endif } else if (exivKey == "Exif.Photo.OECF") { v = kmdOECFStructureToExifOECF(entry.value()); } else if (exivKey == "Exif.Photo.DeviceSettingDescription") { v = deviceSettingDescriptionKMDToExif(entry.value()); } else if (exivKey == "Exif.Photo.CFAPattern") { v = cfaPatternKMDToExif(entry.value()); } else if (exivKey == "Exif.Photo.Flash") { v = flashKMDToExif(entry.value()); } else if (exivKey == "Exif.Photo.UserComment") { Q_ASSERT(entry.value().type() == KisMetaData::Value::LangArray); QMap langArr = entry.value().asLangArray(); if (langArr.contains("x-default")) { #if EXIV2_MAJOR_VERSION == 0 && EXIV2_MINOR_VERSION <= 20 v = kmdValueToExivValue(langArr.value("x-default"), Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId())); #else v = kmdValueToExivValue(langArr.value("x-default"), exifKey.defaultTypeId()); #endif } else if (langArr.size() > 0) { #if EXIV2_MAJOR_VERSION == 0 && EXIV2_MINOR_VERSION <= 20 v = kmdValueToExivValue(langArr.begin().value(), Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId())); #else v = kmdValueToExivValue(langArr.begin().value(), exifKey.defaultTypeId()); #endif } } else { dbgMetaData << exifKey.tag(); #if EXIV2_MAJOR_VERSION == 0 && EXIV2_MINOR_VERSION <= 20 v = kmdValueToExivValue(entry.value(), Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId())); #else v = kmdValueToExivValue(entry.value(), exifKey.defaultTypeId()); #endif } if (v && v->typeId() != Exiv2::invalidTypeId) { dbgMetaData << "Saving key" << exivKey << " of KMD value" << entry.value(); exifData.add(exifKey, v); } else { dbgMetaData << "No exif value was created for" << entry.qualifiedName() << " as" << exivKey;// << " of KMD value" << entry.value(); } } } catch (Exiv2::AnyError& e) { dbgMetaData << "exiv error " << e.what(); } } #if EXIV2_MAJOR_VERSION == 0 && EXIV2_MINOR_VERSION <= 17 Exiv2::DataBuf rawData = exifData.copy(); ioDevice->write((const char*) rawData.pData_, rawData.size_); #else Exiv2::Blob rawData; Exiv2::ExifParser::encode(rawData, Exiv2::littleEndian, exifData); ioDevice->write((const char*) &*rawData.begin(), rawData.size()); #endif ioDevice->close(); return true; } bool KisExifIO::canSaveAllEntries(KisMetaData::Store* /*store*/) const { return false; // It's a known fact that exif can't save all information, but TODO: write the check } bool KisExifIO::loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const { ioDevice->open(QIODevice::ReadOnly); if (!ioDevice->isOpen()) { return false; } QByteArray arr = ioDevice->readAll(); Exiv2::ExifData exifData; Exiv2::ByteOrder byteOrder; #if EXIV2_MAJOR_VERSION == 0 && EXIV2_MINOR_VERSION <= 17 exifData.load((const Exiv2::byte*)arr.data(), arr.size()); byteOrder = exifData.byteOrder(); #else try { byteOrder = Exiv2::ExifParser::decode(exifData, (const Exiv2::byte*)arr.data(), arr.size()); } catch (const std::exception& ex) { warnKrita << "Received exception trying to parse exiv data" << ex.what(); return false; } catch (...) { dbgKrita << "Received unknown exception trying to parse exiv data"; return false; } #endif dbgMetaData << "Byte order = " << byteOrder << ppVar(Exiv2::bigEndian) << ppVar(Exiv2::littleEndian); dbgMetaData << "There are" << exifData.count() << " entries in the exif section"; const KisMetaData::Schema* tiffSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::TIFFSchemaUri); Q_ASSERT(tiffSchema); const KisMetaData::Schema* exifSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::EXIFSchemaUri); Q_ASSERT(exifSchema); const KisMetaData::Schema* dcSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri); Q_ASSERT(dcSchema); const KisMetaData::Schema* xmpSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::XMPSchemaUri); Q_ASSERT(xmpSchema); for (Exiv2::ExifMetadata::const_iterator it = exifData.begin(); it != exifData.end(); ++it) { if (it->key() == "Exif.Photo.StripOffsets" || it->key() == "RowsPerStrip" || it->key() == "StripByteCounts" || it->key() == "JPEGInterchangeFormat" || it->key() == "JPEGInterchangeFormatLength" || it->tagName() == "0x0000" ) { dbgMetaData << it->key().c_str() << " is ignored"; } else if (it->key() == "Exif.Photo.MakerNote") { const KisMetaData::Schema* makerNoteSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::MakerNoteSchemaUri); store->addEntry(KisMetaData::Entry(makerNoteSchema, "RawData", exivValueToKMDValue(it->getValue(), false))); } else if (it->key() == "Exif.Image.DateTime") { // load as xmp:ModifyDate store->addEntry(KisMetaData::Entry(xmpSchema, "ModifyDate", KisMetaData::Value(exivValueToDateTime(it->getValue())))); } else if (it->key() == "Exif.Image.ImageDescription") { // load as "dc:description" store->addEntry(KisMetaData::Entry(dcSchema, "description", exivValueToKMDValue(it->getValue(), false))); } else if (it->key() == "Exif.Image.Software") { // load as "xmp:CreatorTool" store->addEntry(KisMetaData::Entry(xmpSchema, "CreatorTool", exivValueToKMDValue(it->getValue(), false))); } else if (it->key() == "Exif.Image.Artist") { // load as dc:creator QList creators; creators.push_back(exivValueToKMDValue(it->getValue(), false)); store->addEntry(KisMetaData::Entry(dcSchema, "creator", KisMetaData::Value(creators, KisMetaData::Value::OrderedArray))); } else if (it->key() == "Exif.Image.Copyright") { // load as dc:rights store->addEntry(KisMetaData::Entry(dcSchema, "rights", exivValueToKMDValue(it->getValue(), false))); } else if (it->groupName() == "Image") { // Tiff tags QString fixedTN = it->tagName().c_str(); if (it->key() == "Exif.Image.ExifTag") { dbgMetaData << "Ignoring " << it->key().c_str(); } else if (KisMetaData::Entry::isValidName(fixedTN)) { store->addEntry(KisMetaData::Entry(tiffSchema, fixedTN, exivValueToKMDValue(it->getValue(), false))) ; } else { dbgMetaData << "Invalid tag name: " << fixedTN; } } else if (it->groupName() == "Photo" || (it->groupName() == "GPS")) { // Exif tags (and GPS tags) KisMetaData::Value v; if (it->key() == "Exif.Photo.ExifVersion" || it->key() == "Exif.Photo.FlashpixVersion") { v = exifVersionToKMDValue(it->getValue()); } else if (it->key() == "Exif.Photo.FileSource") { v = KisMetaData::Value(3); } else if (it->key() == "Exif.Photo.SceneType") { v = KisMetaData::Value(1); } else if (it->key() == "Exif.Photo.ComponentsConfiguration") { v = exifArrayToKMDIntOrderedArray(it->getValue()); } else if (it->key() == "Exif.Photo.OECF") { v = exifOECFToKMDOECFStructure(it->getValue(), byteOrder); } else if (it->key() == "Exif.Photo.DateTimeDigitized" || it->key() == "Exif.Photo.DateTimeOriginal") { v = KisMetaData::Value(exivValueToDateTime(it->getValue())); } else if (it->key() == "Exif.Photo.DeviceSettingDescription") { v = deviceSettingDescriptionExifToKMD(it->getValue()); } else if (it->key() == "Exif.Photo.CFAPattern") { v = cfaPatternExifToKMD(it->getValue(), byteOrder); } else if (it->key() == "Exif.Photo.Flash") { v = flashExifToKMD(it->getValue()); } else if (it->key() == "Exif.Photo.UserComment") { KisMetaData::Value vUC = exivValueToKMDValue(it->getValue(), false); Q_ASSERT(vUC.type() == KisMetaData::Value::Variant); QVariant commentVar = vUC.asVariant(); QString comment; if (commentVar.type() == QVariant::String) { comment = commentVar.toString(); } else if (commentVar.type() == QVariant::ByteArray) { const QByteArray commentString = commentVar.toByteArray(); comment = QString::fromLatin1(commentString.constData(), commentString.size()); } else { warnKrita << "KisExifIO: Unhandled UserComment value type."; } KisMetaData::Value vcomment(comment); vcomment.addPropertyQualifier("xml:lang", KisMetaData::Value("x-default")); QList alt; alt.append(vcomment); v = KisMetaData::Value(alt, KisMetaData::Value::LangArray); } else { bool forceSeq = false; KisMetaData::Value::ValueType arrayType = KisMetaData::Value::UnorderedArray; if (it->key() == "Exif.Photo.ISOSpeedRatings") { forceSeq = true; arrayType = KisMetaData::Value::OrderedArray; } v = exivValueToKMDValue(it->getValue(), forceSeq, arrayType); } if (it->key() == "Exif.Photo.InteroperabilityTag" || it->key() == "Exif.Photo.0xea1d") { // InteroperabilityTag isn't useful for XMP, 0xea1d isn't a valid Exif tag dbgMetaData << "Ignoring " << it->key().c_str(); } else { store->addEntry(KisMetaData::Entry(exifSchema, it->tagName().c_str(), v)); } } else if (it->groupName() == "Thumbnail") { dbgMetaData << "Ignoring thumbnail tag :" << it->key().c_str(); } else { dbgMetaData << "Unknown exif tag, cannot load:" << it->key().c_str(); } } ioDevice->close(); return true; } diff --git a/libs/ui/kisexiv2/kis_exif_io.h b/libs/ui/kisexiv2/kis_exif_io.h index f6a6198b5e..d9a133c82f 100644 --- a/libs/ui/kisexiv2/kis_exif_io.h +++ b/libs/ui/kisexiv2/kis_exif_io.h @@ -1,53 +1,53 @@ /* * 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. */ #ifndef _KIS_EXIF_IO_H_ #define _KIS_EXIF_IO_H_ -#include +#include #include class KisExifIO : public KisMetaData::IOBackend { struct Private; public: KisExifIO(); ~KisExifIO() override; QString id() const override { return "exif"; } QString name() const override { return i18n("Exif"); } BackendType type() const override { return Binary; } bool supportSaving() const override { return true; } bool saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType headerType = NoHeader) const override; bool canSaveAllEntries(KisMetaData::Store* store) const override; bool supportLoading() const override { return true; } bool loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const override; private: Private* const d; }; #endif diff --git a/libs/ui/kisexiv2/kis_exiv2.cpp b/libs/ui/kisexiv2/kis_exiv2.cpp index ba3f8e904b..87b6fa88a0 100644 --- a/libs/ui/kisexiv2/kis_exiv2.cpp +++ b/libs/ui/kisexiv2/kis_exiv2.cpp @@ -1,296 +1,296 @@ /* * 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; 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 "kis_exiv2.h" #include #include "kis_iptc_io.h" #include "kis_exif_io.h" #include "kis_xmp_io.h" -#include +#include #include // ---- Generic conversion functions ---- // // Convert an exiv value to a KisMetaData value KisMetaData::Value exivValueToKMDValue(const Exiv2::Value::AutoPtr value, bool forceSeq, KisMetaData::Value::ValueType arrayType) { switch (value->typeId()) { case Exiv2::signedByte: case Exiv2::invalidTypeId: case Exiv2::lastTypeId: case Exiv2::directory: dbgMetaData << "Invalid value :" << value->typeId() << " value =" << value->toString().c_str(); return KisMetaData::Value(); case Exiv2::undefined: { dbgMetaData << "Undefined value :" << value->typeId() << " value =" << value->toString().c_str(); QByteArray array(value->count() , 0); value->copy((Exiv2::byte*)array.data(), Exiv2::invalidByteOrder); return KisMetaData::Value(QString(array.toBase64())); } case Exiv2::unsignedByte: case Exiv2::unsignedShort: case Exiv2::unsignedLong: case Exiv2::signedShort: case Exiv2::signedLong: { if (value->count() == 1 && !forceSeq) { return KisMetaData::Value((int)value->toLong()); } else { QList array; for (int i = 0; i < value->count(); i++) array.push_back(KisMetaData::Value((int)value->toLong(i))); return KisMetaData::Value(array, arrayType); } } case Exiv2::asciiString: case Exiv2::string: case Exiv2::comment: // look at kexiv2 for the problem about decoding correctly that tag return KisMetaData::Value(value->toString().c_str()); case Exiv2::unsignedRational: if(value->size() < 2) { dbgMetaData << "Invalid size :" << value->size() << " value =" << value->toString().c_str(); return KisMetaData::Value(); } return KisMetaData::Value(KisMetaData::Rational(value->toRational().first , value->toRational().second)); case Exiv2::signedRational: if(value->size() < 2) { dbgMetaData << "Invalid size :" << value->size() << " value =" << value->toString().c_str(); return KisMetaData::Value(); } return KisMetaData::Value(KisMetaData::Rational(value->toRational().first , value->toRational().second)); case Exiv2::date: case Exiv2::time: return KisMetaData::Value(QDateTime::fromString(value->toString().c_str(), Qt::ISODate)); case Exiv2::xmpText: case Exiv2::xmpAlt: case Exiv2::xmpBag: case Exiv2::xmpSeq: case Exiv2::langAlt: default: { dbgMetaData << "Unknown type id :" << value->typeId() << " value =" << value->toString().c_str(); //Q_ASSERT(false); // This point must never be reached ! return KisMetaData::Value(); } } dbgMetaData << "Unknown type id :" << value->typeId() << " value =" << value->toString().c_str(); //Q_ASSERT(false); // This point must never be reached ! return KisMetaData::Value(); } // Convert a QtVariant to an Exiv value Exiv2::Value* variantToExivValue(const QVariant& variant, Exiv2::TypeId type) { switch (type) { case Exiv2::undefined: { QByteArray arr = QByteArray::fromBase64(variant.toString().toLatin1()); return new Exiv2::DataValue((Exiv2::byte*)arr.data(), arr.size()); } case Exiv2::unsignedByte: return new Exiv2::ValueType(variant.toUInt(0)); case Exiv2::unsignedShort: return new Exiv2::ValueType(variant.toUInt(0)); case Exiv2::unsignedLong: return new Exiv2::ValueType(variant.toUInt(0)); case Exiv2::signedShort: return new Exiv2::ValueType(variant.toInt(0)); case Exiv2::signedLong: return new Exiv2::ValueType(variant.toInt(0)); case Exiv2::date: { QDate date = variant.toDate(); return new Exiv2::DateValue(date.year(), date.month(), date.day()); } case Exiv2::asciiString: if (variant.type() == QVariant::DateTime) { return new Exiv2::AsciiValue(qPrintable(variant.toDateTime().toString("yyyy:MM:dd hh:mm:ss"))); } else return new Exiv2::AsciiValue(qPrintable(variant.toString())); case Exiv2::string: { if (variant.type() == QVariant::DateTime) { return new Exiv2::StringValue(qPrintable(variant.toDateTime().toString("yyyy:MM:dd hh:mm:ss"))); } else return new Exiv2::StringValue(qPrintable(variant.toString())); } case Exiv2::comment: return new Exiv2::CommentValue(qPrintable(variant.toString())); default: dbgMetaData << "Unhandled type:" << type; //Q_ASSERT(false); return 0; } } template Exiv2::Value* arrayToExivValue(const KisMetaData::Value& value) { Exiv2::ValueType<_TYPE_>* ev = new Exiv2::ValueType<_TYPE_>(); for (int i = 0; i < value.asArray().size(); ++i) { ev->value_.push_back(qvariant_cast<_TYPE_>(value.asArray()[i].asVariant())); } return ev; } // Convert a KisMetaData to an Exiv value Exiv2::Value* kmdValueToExivValue(const KisMetaData::Value& value, Exiv2::TypeId type) { switch (value.type()) { case KisMetaData::Value::Invalid: return &*Exiv2::Value::create(Exiv2::invalidTypeId); case KisMetaData::Value::Variant: { return variantToExivValue(value.asVariant(), type); } case KisMetaData::Value::Rational: //Q_ASSERT(type == Exiv2::signedRational || type == Exiv2::unsignedRational); if (type == Exiv2::signedRational) { return new Exiv2::ValueType(Exiv2::Rational(value.asRational().numerator, value.asRational().denominator)); } else { return new Exiv2::ValueType(Exiv2::URational(value.asRational().numerator, value.asRational().denominator)); } case KisMetaData::Value::OrderedArray: /* Falls through */ case KisMetaData::Value::UnorderedArray: /* Falls through */ case KisMetaData::Value::AlternativeArray: { switch (type) { case Exiv2::unsignedByte: return arrayToExivValue(value); case Exiv2::unsignedShort: return arrayToExivValue(value); case Exiv2::unsignedLong: return arrayToExivValue(value); case Exiv2::signedShort: return arrayToExivValue(value); case Exiv2::signedLong: return arrayToExivValue(value); case Exiv2::string: { Exiv2::StringValue* ev = new Exiv2::StringValue(); for (int i = 0; i < value.asArray().size(); ++i) { ev->value_ += qvariant_cast(value.asArray()[i].asVariant()).toLatin1().constData(); if (i != value.asArray().size() - 1) ev->value_ += ','; } return ev; } break; default: dbgMetaData << type << " " << value; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(0 && "Unknown alternative array type", 0); break; } break; } default: dbgMetaData << type << " " << value; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(0 && "Unknown array type", 0); break; } return 0; } Exiv2::Value* kmdValueToExivXmpValue(const KisMetaData::Value& value) { //Q_ASSERT(value.type() != KisMetaData::Value::Structure); switch (value.type()) { case KisMetaData::Value::Invalid: return new Exiv2::DataValue(Exiv2::invalidTypeId); case KisMetaData::Value::Variant: { QVariant var = value.asVariant(); if (var.type() == QVariant::Bool) { if (var.toBool()) { return new Exiv2::XmpTextValue("True"); } else { return new Exiv2::XmpTextValue("False"); } } else { //Q_ASSERT(var.canConvert(QVariant::String)); return new Exiv2::XmpTextValue(var.toString().toLatin1().constData()); } } case KisMetaData::Value::Rational: { QString rat = "%1 / %2"; rat = rat.arg(value.asRational().numerator); rat = rat.arg(value.asRational().denominator); return new Exiv2::XmpTextValue(rat.toLatin1().constData()); } case KisMetaData::Value::AlternativeArray: case KisMetaData::Value::OrderedArray: case KisMetaData::Value::UnorderedArray: { Exiv2::XmpArrayValue* arrV = new Exiv2::XmpArrayValue; switch (value.type()) { case KisMetaData::Value::OrderedArray: arrV->setXmpArrayType(Exiv2::XmpValue::xaSeq); break; case KisMetaData::Value::UnorderedArray: arrV->setXmpArrayType(Exiv2::XmpValue::xaBag); break; case KisMetaData::Value::AlternativeArray: arrV->setXmpArrayType(Exiv2::XmpValue::xaAlt); break; default: // Cannot happen ; } Q_FOREACH (const KisMetaData::Value& v, value.asArray()) { Exiv2::Value* ev = kmdValueToExivXmpValue(v); if (ev) { arrV->read(ev->toString()); delete ev; } } return arrV; } case KisMetaData::Value::LangArray: { Exiv2::Value* arrV = new Exiv2::LangAltValue; QMap langArray = value.asLangArray(); for (QMap::iterator it = langArray.begin(); it != langArray.end(); ++it) { QString exivVal; if (it.key() != "x-default") { exivVal = "lang=" + it.key() + ' '; } //Q_ASSERT(it.value().type() == KisMetaData::Value::Variant); QVariant var = it.value().asVariant(); //Q_ASSERT(var.type() == QVariant::String); exivVal += var.toString(); arrV->read(exivVal.toLatin1().constData()); } return arrV; } case KisMetaData::Value::Structure: default: { warnKrita << "KisExiv2: Unhandled value type"; return 0; } } warnKrita << "KisExiv2: Unhandled value type"; return 0; } void KisExiv2::initialize() { // XXX_EXIV: make the exiv io backends real plugins KisMetaData::IOBackendRegistry* ioreg = KisMetaData::IOBackendRegistry::instance(); ioreg->add(new KisIptcIO); ioreg->add(new KisExifIO); ioreg->add(new KisXMPIO); } diff --git a/libs/ui/kisexiv2/kis_exiv2.h b/libs/ui/kisexiv2/kis_exiv2.h index 070f0cb71d..9343265f1e 100644 --- a/libs/ui/kisexiv2/kis_exiv2.h +++ b/libs/ui/kisexiv2/kis_exiv2.h @@ -1,44 +1,44 @@ /* * 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; 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 _KIS_EXIV2_H_ #define _KIS_EXIV2_H_ -#include +#include #include #include "kritaui_export.h" /// Convert an exiv value to a KisMetaData value KisMetaData::Value exivValueToKMDValue(const Exiv2::Value::AutoPtr value, bool forceSeq, KisMetaData::Value::ValueType arrayType = KisMetaData::Value::UnorderedArray); /// Convert a KisMetaData to an Exiv value Exiv2::Value* kmdValueToExivValue(const KisMetaData::Value& value, Exiv2::TypeId type); /** * Convert a KisMetaData to an Exiv value, without knowing the targeted Exiv2::TypeId * This function should be used for saving to XMP. */ Exiv2::Value* kmdValueToExivXmpValue(const KisMetaData::Value& value); struct KRITAUI_EXPORT KisExiv2 { static void initialize(); }; #endif diff --git a/libs/ui/kisexiv2/kis_iptc_io.cpp b/libs/ui/kisexiv2/kis_iptc_io.cpp index dbed46b493..d2eb7c9b2f 100644 --- a/libs/ui/kisexiv2/kis_iptc_io.cpp +++ b/libs/ui/kisexiv2/kis_iptc_io.cpp @@ -1,200 +1,200 @@ /* * 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; 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 "kis_iptc_io.h" #include #include #include "kis_exiv2.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include const char photoshopMarker[] = "Photoshop 3.0\0"; const char photoshopBimId_[] = "8BIM"; const uint16_t photoshopIptc = 0x0404; const QByteArray photoshopIptc_((char*)&photoshopIptc, 2); struct IPTCToKMD { QString exivTag; QString namespaceUri; QString name; }; static const IPTCToKMD mappings[] = { { "Iptc.Application2.City", KisMetaData::Schema::PhotoshopSchemaUri, "City" }, { "Iptc.Application2.Copyright", KisMetaData::Schema::DublinCoreSchemaUri, "rights" }, { "Iptc.Application2.CountryName", KisMetaData::Schema::PhotoshopSchemaUri, "Country" }, { "Iptc.Application2.CountryCode", KisMetaData::Schema::IPTCSchemaUri, "CountryCode" }, { "Iptc.Application2.Byline", KisMetaData::Schema::DublinCoreSchemaUri, "creator" }, { "Iptc.Application2.BylineTitle", KisMetaData::Schema::PhotoshopSchemaUri, "AuthorsPosition" }, { "Iptc.Application2.DateCreated", KisMetaData::Schema::PhotoshopSchemaUri, "DateCreated" }, { "Iptc.Application2.Caption", KisMetaData::Schema::DublinCoreSchemaUri, "description" }, { "Iptc.Application2.Writer", KisMetaData::Schema::PhotoshopSchemaUri, "CaptionWriter" }, { "Iptc.Application2.Headline", KisMetaData::Schema::PhotoshopSchemaUri, "Headline" }, { "Iptc.Application2.SpecialInstructions", KisMetaData::Schema::PhotoshopSchemaUri, "Instructions" }, { "Iptc.Application2.ObjectAttribute", KisMetaData::Schema::IPTCSchemaUri, "IntellectualGenre" }, { "Iptc.Application2.TransmissionReference", KisMetaData::Schema::PhotoshopSchemaUri, "JobID" }, { "Iptc.Application2.Keywords", KisMetaData::Schema::DublinCoreSchemaUri, "subject" }, { "Iptc.Application2.SubLocation", KisMetaData::Schema::IPTCSchemaUri, "Location" }, { "Iptc.Application2.Credit", KisMetaData::Schema::PhotoshopSchemaUri, "Credit" }, { "Iptc.Application2.ProvinceState", KisMetaData::Schema::PhotoshopSchemaUri, "State" }, { "Iptc.Application2.Source", KisMetaData::Schema::PhotoshopSchemaUri, "Source" }, { "Iptc.Application2.Subject", KisMetaData::Schema::IPTCSchemaUri, "SubjectCode" }, { "Iptc.Application2.ObjectName", KisMetaData::Schema::DublinCoreSchemaUri, "title" }, { "Iptc.Application2.Urgency", KisMetaData::Schema::PhotoshopSchemaUri, "Urgency" }, { "Iptc.Application2.Category", KisMetaData::Schema::PhotoshopSchemaUri, "Category" }, { "Iptc.Application2.SuppCategory", KisMetaData::Schema::PhotoshopSchemaUri, "SupplementalCategory" }, { "", "", "" } // indicates the end of the array }; struct KisIptcIO::Private { QHash iptcToKMD; QHash kmdToIPTC; }; // ---- Implementation of KisExifIO ----// KisIptcIO::KisIptcIO() : d(new Private) { } KisIptcIO::~KisIptcIO() { delete d; } void KisIptcIO::initMappingsTable() const { // For some reason, initializing the tables in the constructor makes the it crash if (d->iptcToKMD.size() == 0) { for (int i = 0; !mappings[i].exivTag.isEmpty(); i++) { dbgKrita << "mapping[i] = " << mappings[i].exivTag << " " << mappings[i].namespaceUri << " " << mappings[i].name; d->iptcToKMD[mappings[i].exivTag] = mappings[i]; d->kmdToIPTC[ KisMetaData::SchemaRegistry::instance() ->schemaFromUri(mappings[i].namespaceUri) ->generateQualifiedName(mappings[i].name)] = mappings[i]; } } } bool KisIptcIO::saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType headerType) const { QStringList blockedEntries = QStringList() << "photoshop:DateCreated"; initMappingsTable(); ioDevice->open(QIODevice::WriteOnly); Exiv2::IptcData iptcData; for (QHash::const_iterator it = store->begin(); it != store->end(); ++it) { const KisMetaData::Entry& entry = *it; if (d->kmdToIPTC.contains(entry.qualifiedName())) { if (blockedEntries.contains(entry.qualifiedName())) { warnKrita << "skipping" << entry.qualifiedName() << entry.value(); continue; } try { QString iptcKeyStr = d->kmdToIPTC[ entry.qualifiedName()].exivTag; Exiv2::IptcKey iptcKey(qPrintable(iptcKeyStr)); Exiv2::Value *v = kmdValueToExivValue(entry.value(), Exiv2::IptcDataSets::dataSetType(iptcKey.tag(), iptcKey.record())); if (v && v->typeId() != Exiv2::invalidTypeId) { iptcData.add(iptcKey, v); } } catch (Exiv2::AnyError& e) { dbgMetaData << "exiv error " << e.what(); } } } #if EXIV2_MAJOR_VERSION == 0 && EXIV2_MINOR_VERSION <= 17 Exiv2::DataBuf rawData = iptcData.copy(); #else Exiv2::DataBuf rawData = Exiv2::IptcParser::encode(iptcData); #endif if (headerType == KisMetaData::IOBackend::JpegHeader) { QByteArray header; header.append(photoshopMarker); header.append(QByteArray(1, 0)); // Null terminated string header.append(photoshopBimId_); header.append(photoshopIptc_); header.append(QByteArray(2, 0)); qint32 size = rawData.size_; QByteArray sizeArray(4, 0); sizeArray[0] = (char)((size & 0xff000000) >> 24); sizeArray[1] = (char)((size & 0x00ff0000) >> 16); sizeArray[2] = (char)((size & 0x0000ff00) >> 8); sizeArray[3] = (char)(size & 0x000000ff); header.append(sizeArray); ioDevice->write(header); } ioDevice->write((const char*) rawData.pData_, rawData.size_); ioDevice->close(); return true; } bool KisIptcIO::canSaveAllEntries(KisMetaData::Store* store) const { Q_UNUSED(store); return false; } bool KisIptcIO::loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const { initMappingsTable(); dbgMetaData << "Loading IPTC Tags"; ioDevice->open(QIODevice::ReadOnly); QByteArray arr = ioDevice->readAll(); Exiv2::IptcData iptcData; #if EXIV2_MAJOR_VERSION == 0 && EXIV2_MINOR_VERSION <= 17 iptcData.load((const Exiv2::byte*)arr.data(), arr.size()); #else Exiv2::IptcParser::decode(iptcData, (const Exiv2::byte*)arr.data(), arr.size()); #endif dbgMetaData << "There are" << iptcData.count() << " entries in the IPTC section"; for (Exiv2::IptcMetadata::const_iterator it = iptcData.begin(); it != iptcData.end(); ++it) { dbgMetaData << "Reading info for key" << it->key().c_str(); if (d->iptcToKMD.contains(it->key().c_str())) { const IPTCToKMD& iptcToKMd = d->iptcToKMD[it->key().c_str()]; const KisMetaData::Schema* schema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(iptcToKMd.namespaceUri); KisMetaData::Value value; if (iptcToKMd.exivTag == "Iptc.Application2.Keywords") { Q_ASSERT(it->getValue()->typeId() == Exiv2::string); QString data = it->getValue()->toString().c_str(); QStringList list = data.split(','); QList values; Q_FOREACH (const QString &entry, list) { values.push_back(KisMetaData::Value(entry)); } value = KisMetaData::Value(values, KisMetaData::Value::UnorderedArray); } else { value = exivValueToKMDValue(it->getValue(), false); } store->addEntry(KisMetaData::Entry(schema, iptcToKMd.name, value)); } } return false; } diff --git a/libs/ui/kisexiv2/kis_iptc_io.h b/libs/ui/kisexiv2/kis_iptc_io.h index 6d583272b6..bbac8a5374 100644 --- a/libs/ui/kisexiv2/kis_iptc_io.h +++ b/libs/ui/kisexiv2/kis_iptc_io.h @@ -1,56 +1,56 @@ /* * 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; 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 _KIS_IPTC_IO_H_ #define _KIS_IPTC_IO_H_ -#include +#include #include class KisIptcIO : public KisMetaData::IOBackend { struct Private; public: KisIptcIO(); ~KisIptcIO() override; QString id() const override { return "iptc"; } QString name() const override { return i18n("Iptc"); } BackendType type() const override { return Binary; } bool supportSaving() const override { return true; } bool saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType headerType = NoHeader) const override; bool canSaveAllEntries(KisMetaData::Store* store) const override; bool supportLoading() const override { return true; } bool loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const override; private: void initMappingsTable() const; private: Private* const d; }; #endif diff --git a/libs/ui/kisexiv2/kis_xmp_io.cpp b/libs/ui/kisexiv2/kis_xmp_io.cpp index 8fe0afcf01..c4663efe7d 100644 --- a/libs/ui/kisexiv2/kis_xmp_io.cpp +++ b/libs/ui/kisexiv2/kis_xmp_io.cpp @@ -1,401 +1,401 @@ /* * Copyright (c) 2008-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_xmp_io.h" #include #include #include "kis_exiv2.h" -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include KisXMPIO::KisXMPIO() { } KisXMPIO::~KisXMPIO() { } inline std::string exiv2Prefix(const KisMetaData::Schema* _schema) { const QByteArray latin1SchemaUri = _schema->uri().toLatin1(); std::string prefix = Exiv2::XmpProperties::prefix(latin1SchemaUri.constData()); if (prefix.empty()) { dbgMetaData << "Unknown namespace " << ppVar(_schema->uri()) << ppVar(_schema->prefix()); prefix = _schema->prefix().toLatin1().constData(); Exiv2::XmpProperties::registerNs(latin1SchemaUri.constData(), prefix); } return prefix; } namespace { void saveStructure(Exiv2::XmpData& xmpData_, const QString& name, const std::string& prefix, const QMap& structure, const KisMetaData::Schema* structureSchema) { std::string structPrefix = exiv2Prefix(structureSchema); for (QMap::const_iterator it = structure.begin(); it != structure.end(); ++it) { Q_ASSERT(it.value().type() != KisMetaData::Value::Structure); // Can't nest structure QString key = QString("%1/%2:%3").arg(name).arg(structPrefix.c_str()).arg(it.key()); Exiv2::XmpKey ekey(prefix, key.toLatin1().constData()); dbgMetaData << ppVar(key) << ppVar(ekey.key().c_str()); Exiv2::Value *v = kmdValueToExivXmpValue(it.value()); if (v) { xmpData_.add(ekey, v); } } } } bool KisXMPIO::saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType headerType) const { dbgMetaData << "Save XMP Data"; Exiv2::XmpData xmpData_; for (QHash::const_iterator it = store->begin(); it != store->end(); ++it) { const KisMetaData::Entry& entry = *it; // Check whether the prefix and namespace are know to exiv2 std::string prefix = exiv2Prefix(entry.schema()); dbgMetaData << "Saving " << entry.name(); const KisMetaData::Value& value = entry.value(); const KisMetaData::TypeInfo* typeInfo = entry.schema()->propertyType(entry.name()); if (value.type() == KisMetaData::Value::Structure) { QMap structure = value.asStructure(); const KisMetaData::Schema* structureSchema = 0; if (typeInfo) { structureSchema = typeInfo->structureSchema(); } if (!structureSchema) { dbgMetaData << "Unknown schema for " << entry.name(); structureSchema = entry.schema(); } Q_ASSERT(structureSchema); saveStructure(xmpData_, entry.name(), prefix, structure, structureSchema); } else { Exiv2::XmpKey key(prefix, entry.name().toLatin1().constData()); if (typeInfo && (typeInfo->propertyType() == KisMetaData::TypeInfo::OrderedArrayType || typeInfo->propertyType() == KisMetaData::TypeInfo::UnorderedArrayType || typeInfo->propertyType() == KisMetaData::TypeInfo::AlternativeArrayType) && typeInfo->embeddedPropertyType()->propertyType() == KisMetaData::TypeInfo::StructureType) { // Here is the bad part, again we need to do it by hand Exiv2::XmpTextValue tv; switch (typeInfo->propertyType()) { case KisMetaData::TypeInfo::OrderedArrayType: tv.setXmpArrayType(Exiv2::XmpValue::xaSeq); break; case KisMetaData::TypeInfo::UnorderedArrayType: tv.setXmpArrayType(Exiv2::XmpValue::xaBag); break; case KisMetaData::TypeInfo::AlternativeArrayType: tv.setXmpArrayType(Exiv2::XmpValue::xaAlt); break; default: // Cannot happen ; } xmpData_.add(key, &tv); // set the arrya type const KisMetaData::TypeInfo* stuctureTypeInfo = typeInfo->embeddedPropertyType(); const KisMetaData::Schema* structureSchema = 0; if (stuctureTypeInfo) { structureSchema = stuctureTypeInfo->structureSchema(); } if (!structureSchema) { dbgMetaData << "Unknown schema for " << entry.name(); structureSchema = entry.schema(); } Q_ASSERT(structureSchema); QList array = value.asArray(); for (int idx = 0; idx < array.size(); ++idx) { saveStructure(xmpData_, QString("%1[%2]").arg(entry.name()).arg(idx + 1), prefix, array[idx].asStructure(), structureSchema); } } else { dbgMetaData << ppVar(key.key().c_str()); Exiv2::Value *v = kmdValueToExivXmpValue(value); if (v) { xmpData_.add(key, v); } } } // TODO property qualifier } // Serialize data std::string xmpPacket_; Exiv2::XmpParser::encode(xmpPacket_, xmpData_); // Save data into the IO device ioDevice->open(QIODevice::WriteOnly); if (headerType == KisMetaData::IOBackend::JpegHeader) { xmpPacket_ = "http://ns.adobe.com/xap/1.0/\0" + xmpPacket_; } ioDevice->write(xmpPacket_.c_str(), xmpPacket_.length()); return true; } bool parseTagName(const QString &tagString, QString &structName, int &arrayIndex, QString &tagName, const KisMetaData::TypeInfo** typeInfo, const KisMetaData::Schema *schema) { arrayIndex = -1; *typeInfo = 0; int numSubNames = tagString.count('/') + 1; if (numSubNames == 1) { structName.clear(); tagName = tagString; *typeInfo = schema->propertyType(tagName); return true; } if (numSubNames == 2) { QRegExp regexp("([A-Za-z]\\w+)/([A-Za-z]\\w+):([A-Za-z]\\w+)"); if (regexp.indexIn(tagString) != -1) { structName = regexp.capturedTexts()[1]; tagName = regexp.capturedTexts()[3]; *typeInfo = schema->propertyType(structName); if (*typeInfo && (*typeInfo)->propertyType() == KisMetaData::TypeInfo::StructureType) { *typeInfo = (*typeInfo)->structureSchema()->propertyType(tagName); } return true; } QRegExp regexp2("([A-Za-z]\\w+)\\[(\\d+)\\]/([A-Za-z]\\w+):([A-Za-z]\\w+)"); if (regexp2.indexIn(tagString) != -1) { structName = regexp2.capturedTexts()[1]; arrayIndex = regexp2.capturedTexts()[2].toInt() - 1; tagName = regexp2.capturedTexts()[4]; if (schema->propertyType(structName)) { *typeInfo = schema->propertyType(structName)->embeddedPropertyType(); Q_ASSERT(*typeInfo); if ((*typeInfo)->propertyType() == KisMetaData::TypeInfo::StructureType) { *typeInfo = (*typeInfo)->structureSchema()->propertyType(tagName); } } return true; } } warnKrita << "WARNING: Unsupported tag. We do not yet support nested tags. The tag will be dropped!"; warnKrita << " Failing tag:" << tagString; return false; } bool KisXMPIO::loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const { ioDevice->open(QIODevice::ReadOnly); dbgMetaData << "Load XMP Data"; std::string xmpPacket_; QByteArray arr = ioDevice->readAll(); xmpPacket_.assign(arr.data(), arr.length()); dbgMetaData << xmpPacket_.length(); // dbgMetaData << xmpPacket_.c_str(); Exiv2::XmpData xmpData_; Exiv2::XmpParser::decode(xmpData_, xmpPacket_); QMap< const KisMetaData::Schema*, QMap > > structures; QMap< const KisMetaData::Schema*, QMap > > > arraysOfStructures; for (Exiv2::XmpData::iterator it = xmpData_.begin(); it != xmpData_.end(); ++it) { dbgMetaData << "Start iteration" << it->key().c_str(); Exiv2::XmpKey key(it->key()); dbgMetaData << key.groupName().c_str() << " " << key.tagName().c_str() << " " << key.ns().c_str(); if ((key.groupName() == "exif" || key.groupName() == "tiff") && key.tagName() == "NativeDigest") { // TODO: someone who has time to lose can look in adding support for NativeDigest, it's undocumented use by the XMP SDK to check if exif data has been changed while XMP hasn't been updated dbgMetaData << "dropped"; } else { const KisMetaData::Schema* schema = KisMetaData::SchemaRegistry::instance()->schemaFromPrefix(key.groupName().c_str()); if (!schema) { schema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(key.ns().c_str()); if (!schema) { schema = KisMetaData::SchemaRegistry::instance()->create(key.ns().c_str(), key.groupName().c_str()); Q_ASSERT(schema); } } const Exiv2::Value::AutoPtr value = it->getValue(); QString structName; int arrayIndex = -1; QString tagName; const KisMetaData::TypeInfo* typeInfo = 0; if (!parseTagName(key.tagName().c_str(), structName, arrayIndex, tagName, &typeInfo, schema)) continue; bool isStructureEntry = !structName.isEmpty() && arrayIndex == -1; bool isStructureInArrayEntry = !structName.isEmpty() && arrayIndex != -1; Q_ASSERT(isStructureEntry != isStructureInArrayEntry || !isStructureEntry); KisMetaData::Value v; bool ignoreValue = false; // Compute the value if (value->typeId() == Exiv2::xmpBag || value->typeId() == Exiv2::xmpSeq || value->typeId() == Exiv2::xmpAlt) { const KisMetaData::TypeInfo* embeddedTypeInfo = 0; if (typeInfo) { embeddedTypeInfo = typeInfo->embeddedPropertyType(); } const KisMetaData::Parser* parser = 0; if (embeddedTypeInfo) { parser = embeddedTypeInfo->parser(); } const Exiv2::XmpArrayValue* xav = dynamic_cast(value.get()); Q_ASSERT(xav); QList array; for (std::vector< std::string >::const_iterator it = xav->value_.begin(); it != xav->value_.end(); ++it) { QString value = it->c_str(); if (parser) { array.push_back(parser->parse(value)); } else { dbgImage << "No parser " << tagName; array.push_back(KisMetaData::Value(value)); } } KisMetaData::Value::ValueType vt = KisMetaData::Value::Invalid; switch (xav->xmpArrayType()) { case Exiv2::XmpValue::xaNone: warnKrita << "KisXMPIO: Unsupported array"; break; case Exiv2::XmpValue::xaAlt: vt = KisMetaData::Value::AlternativeArray; break; case Exiv2::XmpValue::xaBag: vt = KisMetaData::Value::UnorderedArray; break; case Exiv2::XmpValue::xaSeq: vt = KisMetaData::Value::OrderedArray; break; } v = KisMetaData::Value(array, vt); } else if (value->typeId() == Exiv2::langAlt) { const Exiv2::LangAltValue* xav = dynamic_cast(value.get()); QList alt; for (std::map< std::string, std::string>::const_iterator it = xav->value_.begin(); it != xav->value_.end(); ++it) { KisMetaData::Value valt(it->second.c_str()); valt.addPropertyQualifier("xml:lang", KisMetaData::Value(it->first.c_str())); alt.push_back(valt); } v = KisMetaData::Value(alt, KisMetaData::Value::LangArray); } else { QString valTxt = value->toString().c_str(); if (typeInfo && typeInfo->parser()) { v = typeInfo->parser()->parse(valTxt); } else { dbgMetaData << "No parser " << tagName; v = KisMetaData::Value(valTxt); } if (valTxt == "type=\"Struct\"") { if (!typeInfo || typeInfo->propertyType() == KisMetaData::TypeInfo::StructureType) { ignoreValue = true; } } } // set the value if (isStructureEntry) { structures[schema][structName][tagName] = v; } else if (isStructureInArrayEntry) { if (arraysOfStructures[schema][structName].size() <= arrayIndex) { arraysOfStructures[schema][structName].resize(arrayIndex + 1); } if (!arraysOfStructures[schema][structName][arrayIndex].contains(tagName)) { arraysOfStructures[schema][structName][arrayIndex][tagName] = v; } else { warnKrita << "WARNING: trying to overwrite tag" << tagName << "in" << structName << arrayIndex; } } else { if (!ignoreValue) { store->addEntry(KisMetaData::Entry(schema, tagName, v)); } else { dbgMetaData << "Ignoring value for " << tagName << " " << v; } } } } for (QMap< const KisMetaData::Schema*, QMap > >::iterator it = structures.begin(); it != structures.end(); ++it) { const KisMetaData::Schema* schema = it.key(); for (QMap >::iterator it2 = it.value().begin(); it2 != it.value().end(); ++it2) { store->addEntry(KisMetaData::Entry(schema, it2.key(), KisMetaData::Value(it2.value()))); } } for (QMap< const KisMetaData::Schema*, QMap > > >::iterator it = arraysOfStructures.begin(); it != arraysOfStructures.end(); ++it) { const KisMetaData::Schema* schema = it.key(); for (QMap > >::iterator it2 = it.value().begin(); it2 != it.value().end(); ++it2) { KisMetaData::Value::ValueType type = KisMetaData::Value::OrderedArray; QString entryName = it2.key(); if (schema->propertyType(entryName)) { switch (schema->propertyType(entryName)->propertyType()) { case KisMetaData::TypeInfo::OrderedArrayType: type = KisMetaData::Value::OrderedArray; break; case KisMetaData::TypeInfo::UnorderedArrayType: type = KisMetaData::Value::OrderedArray; break; case KisMetaData::TypeInfo::AlternativeArrayType: type = KisMetaData::Value::AlternativeArray; break; default: type = KisMetaData::Value::Invalid; break; } } else if (store->containsEntry(schema, entryName)) { KisMetaData::Value value = store->getEntry(schema, entryName).value(); if (value.isArray()) { type = value.type(); } } store->removeEntry(schema, entryName); if (type != KisMetaData::Value::Invalid) { QList< KisMetaData::Value > valueList; for (int i = 0; i < it2.value().size(); ++i) { valueList.append(it2.value()[i]); } store->addEntry(KisMetaData::Entry(schema, entryName, KisMetaData::Value(valueList, type))); } } } return true; } diff --git a/libs/ui/kisexiv2/kis_xmp_io.h b/libs/ui/kisexiv2/kis_xmp_io.h index 378f49ade6..e3d92004bc 100644 --- a/libs/ui/kisexiv2/kis_xmp_io.h +++ b/libs/ui/kisexiv2/kis_xmp_io.h @@ -1,53 +1,53 @@ /* * 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; 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_XMP_IO_H_ #define _KIS_XMP_IO_H_ -#include +#include #include class KisXMPIO : public KisMetaData::IOBackend { struct Private; public: KisXMPIO(); ~KisXMPIO() override; QString id() const override { return "xmp"; } QString name() const override { return i18n("XMP"); } BackendType type() const override { return Text; } bool supportSaving() const override { return true; } bool saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType headerType = NoHeader) const override; bool canSaveAllEntries(KisMetaData::Store*) const override { return true; } bool supportLoading() const override { return true; } bool loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const override; }; #endif diff --git a/libs/ui/operations/kis_filter_selection_operation.cpp b/libs/ui/operations/kis_filter_selection_operation.cpp index d0e3226bb9..ed02df1457 100644 --- a/libs/ui/operations/kis_filter_selection_operation.cpp +++ b/libs/ui/operations/kis_filter_selection_operation.cpp @@ -1,58 +1,58 @@ /* * Copyright (c) 2012 Dmitry Kazakov * 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_filter_selection_operation.h" -#include +#include #include #include #include #include #include #include #include #include void KisFilterSelectionOperation::runFilter(KisSelectionFilter* filter, KisViewManager* view, const KisOperationConfiguration& config) { KisSelectionSP selection = view->selection(); if (!selection) return; struct FilterSelection : public KisTransactionBasedCommand { FilterSelection(KisImageSP image, KisSelectionSP sel, KisSelectionFilter *filter) : m_image(image), m_sel(sel), m_filter(filter) {} ~FilterSelection() override { delete m_filter;} KisImageSP m_image; KisSelectionSP m_sel; KisSelectionFilter *m_filter; KUndo2Command* paint() override { KisPixelSelectionSP mergedSelection = m_sel->pixelSelection(); KisTransaction transaction(mergedSelection); QRect processingRect = m_filter->changeRect(mergedSelection->selectedExactRect()); m_filter->process(mergedSelection, processingRect); return transaction.endAndTake(); } }; KisProcessingApplicator *ap = beginAction(view, filter->name()); ap->applyCommand(new FilterSelection(view->image(), selection, filter), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); endAction(ap, config.toXML()); } diff --git a/libs/ui/tests/kis_exiv2_test.cpp b/libs/ui/tests/kis_exiv2_test.cpp index 155d8e5728..f663175863 100644 --- a/libs/ui/tests/kis_exiv2_test.cpp +++ b/libs/ui/tests/kis_exiv2_test.cpp @@ -1,110 +1,110 @@ /* * Copyright (c) 2009 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_exiv2_test.h" #include #include #include #include "kis_debug.h" #include "kis_meta_data_entry.h" #include "kis_meta_data_io_backend.h" #include "kis_meta_data_schema.h" #include "kis_meta_data_schema_registry.h" #include "kis_meta_data_store.h" #include "kis_meta_data_validator.h" -#include +#include #include "kisexiv2/kis_exiv2.h" #include "filestest.h" #include "sdk/tests/kistest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the metadata parser in krita" #endif using namespace KisMetaData; void KisExiv2Test::testExifLoader() { KisExiv2::initialize(); IOBackend* exifIO = IOBackendRegistry::instance()->get("exif"); QVERIFY(exifIO); QFile exifFile(QString(FILES_DATA_DIR) + "/metadata/hpim3238.exv"); exifFile.open(QIODevice::ReadOnly); exifFile.seek(17); QByteArray exifBytes = exifFile.readAll(); QBuffer exifBuffer(&exifBytes); Store* store = new Store; bool loadSuccess = exifIO->loadFrom(store, &exifBuffer); QVERIFY(loadSuccess); Validator validator(store); for (QMap::const_iterator it = validator.invalidEntries().begin(); it != validator.invalidEntries().end(); ++it) { dbgKrita << it.key() << " = " << it.value().type() << " entry = " << store->getEntry(it.key()); } QCOMPARE(validator.countInvalidEntries(), 0); QCOMPARE(validator.countValidEntries(), 51); const KisMetaData::Schema* tiffSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::TIFFSchemaUri); QCOMPARE(store->getEntry(tiffSchema, "Make").value(), Value("Hewlett-Packard")); QCOMPARE(store->getEntry(tiffSchema, "Model").value(), Value("HP PhotoSmart R707 (V01.00) ")); QCOMPARE(store->getEntry(tiffSchema, "Orientation").value(), Value(1)); QCOMPARE(store->getEntry(tiffSchema, "XResolution").value(), Value(Rational(72 / 1))); QCOMPARE(store->getEntry(tiffSchema, "YResolution").value(), Value(Rational(72 / 1))); QCOMPARE(store->getEntry(tiffSchema, "ResolutionUnit").value(), Value(2)); QCOMPARE(store->getEntry(tiffSchema, "YCbCrPositioning").value(), Value(1)); const KisMetaData::Schema* exifSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::EXIFSchemaUri); QCOMPARE(store->getEntry(exifSchema, "ExposureTime").value(), Value(Rational(35355, 100000))); QCOMPARE(store->getEntry(exifSchema, "FNumber").value(), Value(Rational(280, 100))); QCOMPARE(store->getEntry(exifSchema, "ExposureProgram").value(), Value(2)); // QCOMPARE(store->getEntry(exifSchema, "ISOSpeedRatings").value(), Value(100)); // TODO it's a list // TODO test OECF QCOMPARE(store->getEntry(exifSchema, "ExifVersion").value(), Value("0220")); QCOMPARE(store->getEntry(exifSchema, "DateTimeOriginal").value(), Value(QDateTime(QDate(2007, 5, 8), QTime(0, 19, 18)))); QCOMPARE(store->getEntry(exifSchema, "DateTimeDigitized").value(), Value(QDateTime(QDate(2007, 5, 8), QTime(0, 19, 18)))); // TODO ComponentsConfiguration QCOMPARE(store->getEntry(exifSchema, "ShutterSpeedValue").value(), Value(Rational(384, 256))); QCOMPARE(store->getEntry(exifSchema, "ApertureValue").value(), Value(Rational(780, 256))); QCOMPARE(store->getEntry(exifSchema, "BrightnessValue").value(), Value(Rational(-37, 256))); QCOMPARE(store->getEntry(exifSchema, "ExposureBiasValue").value(), Value(Rational(256, 256))); QCOMPARE(store->getEntry(exifSchema, "MaxApertureValue").value(), Value(Rational(280, 100))); QCOMPARE(store->getEntry(exifSchema, "SubjectDistance").value(), Value(Rational(65535, 1000))); const KisMetaData::Schema* dcSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri); Q_UNUSED(dcSchema); const KisMetaData::Schema* xmpSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::XMPSchemaUri); QCOMPARE(store->getEntry(xmpSchema, "CreatorTool").value(), Value("digiKam-0.9.1")); QCOMPARE(store->getEntry(xmpSchema, "ModifyDate").value(), Value(QDateTime(QDate(2007, 5, 8), QTime(0, 19, 18)))); const KisMetaData::Schema* mknSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::MakerNoteSchemaUri); QCOMPARE(store->getEntry(mknSchema, "RawData").value(), Value("SFBNZXQ=")); } KISTEST_MAIN(KisExiv2Test) diff --git a/libs/ui/tests/kis_node_manager_test.cpp b/libs/ui/tests/kis_node_manager_test.cpp index c8c293d083..9486dfbf0e 100644 --- a/libs/ui/tests/kis_node_manager_test.cpp +++ b/libs/ui/tests/kis_node_manager_test.cpp @@ -1,292 +1,188 @@ /* * 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_node_manager_test.h" #include #include #include #include "ui_manager_test.h" class NodeManagerTester : public TestUtil::UiManagerTest { public: NodeManagerTester() : UiManagerTest(false, true, "node_manager_test") { nodeManager = view->nodeManager(); } void activateShapeLayer() { KisNodeSP shape = findNode(image->root(), "shape"); Q_ASSERT(shape); nodeManager->slotNonUiActivatedNode(shape); } KisNodeSP findCloneLayer() { return findNode(image->root(), "clone1"); } void activateCloneLayer() { KisNodeSP node = findCloneLayer(); Q_ASSERT(node); nodeManager->slotNonUiActivatedNode(findCloneLayer()); } KisNodeSP findBlurLayer() { return findNode(image->root(), "blur1"); } void activateBlurLayer() { KisNodeSP node = findBlurLayer(); Q_ASSERT(node); nodeManager->slotNonUiActivatedNode(findBlurLayer()); } void checkUndoWait() { undoStore->undo(); QTest::qWait(1000); image->waitForDone(); QVERIFY(checkLayersInitial()); } KisNodeManager *nodeManager; }; -void testRotateNode(bool useShapeLayer, const QString &name) -{ - NodeManagerTester t; - if(useShapeLayer) { - t.activateShapeLayer(); - } - - t.nodeManager->rotate(M_PI / 6.0); - QTest::qWait(1000); - t.image->waitForDone(); - QVERIFY(t.checkLayersFuzzy(name)); - - t.checkUndoWait(); - t.startConcurrentTask(); - - t.nodeManager->rotate(M_PI / 6.0); - QTest::qWait(1000); - t.image->waitForDone(); - - if (!useShapeLayer) { - QEXPECT_FAIL("", "The user may run Rotate Layer concurrently. It will cause wrong image/selection size fetched for the crop. There is some barrier needed. At least it doesn't crash.", Continue); - } - QVERIFY(t.checkLayersFuzzy(name)); -} - -void testShearNode(bool useShapeLayer, const QString &name) -{ - NodeManagerTester t; - if(useShapeLayer) { - t.activateShapeLayer(); - } - - t.nodeManager->shear(30, 0); - QTest::qWait(1000); - t.image->waitForDone(); - QVERIFY(t.checkLayersFuzzy(name)); - - t.checkUndoWait(); - t.startConcurrentTask(); - - t.nodeManager->shear(30, 0); - QTest::qWait(1000); - t.image->waitForDone(); - - QEXPECT_FAIL("", "The user may run Shear Layer concurrently. It will cause wrong image/selection size fetched for the crop. There is some barrier needed. At least it doesn't crash.", Continue); - QVERIFY(t.checkLayersFuzzy(name)); -} - -void testScaleNode(bool useShapeLayer, const QString &name) -{ - KisFilterStrategy *strategy = new KisBicubicFilterStrategy(); - - NodeManagerTester t; - if(useShapeLayer) { - t.activateShapeLayer(); - } - - t.nodeManager->scale(0.5, 0.5, strategy); - QTest::qWait(1000); - t.image->waitForDone(); - QVERIFY(t.checkLayersFuzzy(name)); - - t.checkUndoWait(); - t.startConcurrentTask(); - - t.nodeManager->scale(0.5, 0.5, strategy); - QTest::qWait(1000); - t.image->waitForDone(); - - QVERIFY(t.checkLayersFuzzy(name)); - - delete strategy; -} - void testMirrorNode(bool useShapeLayer, const QString &name, bool mirrorX) { NodeManagerTester t; if(useShapeLayer) { t.activateShapeLayer(); } if (mirrorX) { t.nodeManager->mirrorNodeX(); } else { t.nodeManager->mirrorNodeY(); } QTest::qWait(1000); t.image->waitForDone(); QVERIFY(t.checkLayersFuzzy(name)); t.checkUndoWait(); t.startConcurrentTask(); if (mirrorX) { t.nodeManager->mirrorNodeX(); } else { t.nodeManager->mirrorNodeY(); } QTest::qWait(1000); t.image->waitForDone(); QEXPECT_FAIL("", "The user may run Mirror Layer concurrently, but it is not ported to strokes yet. At least it doesn't crash.", Continue); QVERIFY(t.checkLayersFuzzy(name)); } -void KisNodeManagerTest::testRotatePaintNode() -{ - testRotateNode(false, "paint_rotated_30"); -} - -void KisNodeManagerTest::testShearPaintNode() -{ - testShearNode(false, "paint_shear_30"); -} - -void KisNodeManagerTest::testScalePaintNode() -{ - testScaleNode(false, "paint_scale_0.5"); -} - void KisNodeManagerTest::testMirrorXPaintNode() { testMirrorNode(false, "paint_mirrorX", true); } void KisNodeManagerTest::testMirrorYPaintNode() { testMirrorNode(false, "paint_mirrorY", false); } -void KisNodeManagerTest::testRotateShapeNode() -{ - testRotateNode(true, "shape_rotated_30"); -} - -void KisNodeManagerTest::testShearShapeNode() -{ - testShearNode(true, "shape_shear_30"); -} - -void KisNodeManagerTest::testScaleShapeNode() -{ - testScaleNode(true, "shape_scale_0.5"); -} - void KisNodeManagerTest::testMirrorShapeNode() { testMirrorNode(true, "shape_mirrorX", true); } void KisNodeManagerTest::testConvertCloneToPaintLayer() { NodeManagerTester t; t.activateCloneLayer(); QVERIFY(t.checkLayersInitial()); t.nodeManager->convertNode("KisPaintLayer"); KisNodeSP node = t.findCloneLayer(); QTest::qWait(1000); t.image->waitForDone(); QVERIFY(dynamic_cast(node.data())); // No visible change should happen! QVERIFY(t.checkLayersInitial()); } void testConvertToSelectionMask(bool fromClone) { NodeManagerTester t; if (fromClone) { t.activateCloneLayer(); } else { t.activateBlurLayer(); } QVERIFY(t.checkLayersInitial()); QVERIFY(!t.image->globalSelection()); t.nodeManager->convertNode("KisSelectionMask"); QTest::qWait(1000); t.image->waitForDone(); KisNodeSP node; if (fromClone) { node = t.findCloneLayer(); } else { node = t.findBlurLayer(); } QVERIFY(!node); KisSelectionSP selection = t.image->globalSelection(); QVERIFY(selection); QVERIFY(!selection->outlineCacheValid() || !selection->outlineCache().isEmpty()); QString testName = fromClone ? "selection_from_clone_layer" : "selection_from_blur_layer"; QVERIFY(t.checkSelectionOnly(testName)); } void KisNodeManagerTest::testConvertCloneToSelectionMask() { testConvertToSelectionMask(true); } void KisNodeManagerTest::testConvertBlurToSelectionMask() { testConvertToSelectionMask(false); } KISTEST_MAIN(KisNodeManagerTest) diff --git a/libs/ui/tests/kis_node_manager_test.h b/libs/ui/tests/kis_node_manager_test.h index 0401b95a1b..458d512213 100644 --- a/libs/ui/tests/kis_node_manager_test.h +++ b/libs/ui/tests/kis_node_manager_test.h @@ -1,45 +1,39 @@ /* * 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. */ #ifndef __KIS_NODE_MANAGER_TEST_H #define __KIS_NODE_MANAGER_TEST_H #include class KisNodeManagerTest : public QObject { Q_OBJECT private Q_SLOTS: - void testRotatePaintNode(); - void testShearPaintNode(); - void testScalePaintNode(); void testMirrorXPaintNode(); void testMirrorYPaintNode(); - void testRotateShapeNode(); - void testShearShapeNode(); - void testScaleShapeNode(); void testMirrorShapeNode(); void testConvertCloneToPaintLayer(); void testConvertCloneToSelectionMask(); void testConvertBlurToSelectionMask(); }; #endif /* __KIS_NODE_MANAGER_TEST_H */ diff --git a/libs/ui/tests/kis_shape_layer_test.cpp b/libs/ui/tests/kis_shape_layer_test.cpp index d857baf45a..356fd9443a 100644 --- a/libs/ui/tests/kis_shape_layer_test.cpp +++ b/libs/ui/tests/kis_shape_layer_test.cpp @@ -1,310 +1,310 @@ /* * Copyright (c) 2018 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_shape_layer_test.h" #include #include "kis_global.h" #include "kis_shape_layer.h" #include #include #include "testutil.h" #include #include -#include -#include +#include +#include #include "kis_filter_strategy.h" #include "kis_layer_utils.h" #include void testMergeDownImpl(bool useImageTransformations) { const QString testName = useImageTransformations ? "scale_and_merge_down" : "merge_down"; using namespace TestUtil; ReferenceImageChecker chk(testName, "shape_layer_test", ReferenceImageChecker::InternalStorage); chk.setMaxFailingPixels(10); QScopedPointer doc(KisPart::instance()->createDocument()); const QRect refRect(0,0,64,64); MaskParent p(refRect); const qreal resolution = 72.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); KisShapeLayerSP shapeLayer1 = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 150); { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(5, 5)); path->lineTo(QPointF(5, 55)); path->lineTo(QPointF(20, 55)); path->lineTo(QPointF(20, 5)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("shape1"); path->setZIndex(1); shapeLayer1->addShape(path); } KisShapeLayerSP shapeLayer2 = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer2", 255); { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(15, 10)); path->lineTo(QPointF(15, 55)); path->lineTo(QPointF(50, 55)); path->lineTo(QPointF(50, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::green))); path->setName("shape2"); path->setZIndex(1); shapeLayer2->addShape(path); } p.image->addNode(shapeLayer1); p.image->addNode(shapeLayer2); shapeLayer1->setDirty(); shapeLayer2->setDirty(); p.waitForImageAndShapeLayers(); QCOMPARE(int(p.image->root()->childCount()), 3); chk.checkImage(p.image, "00_initial_layer_update"); if (useImageTransformations) { KisFilterStrategy *strategy = new KisBilinearFilterStrategy(); p.image->scaleImage(QSize(32, 32), p.image->xRes(), p.image->yRes(), strategy); p.waitForImageAndShapeLayers(); chk.checkImage(p.image, "01_after_scale_down"); } p.image->mergeDown(shapeLayer2, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.waitForImageAndShapeLayers(); QCOMPARE(int(p.image->root()->childCount()), 2); KisShapeLayer *newShapeLayer = dynamic_cast(p.image->root()->lastChild().data()); QVERIFY(newShapeLayer); QVERIFY(newShapeLayer != shapeLayer1.data()); QVERIFY(newShapeLayer != shapeLayer2.data()); chk.checkImage(p.image, "02_after_merge_down"); QVERIFY(chk.testPassed()); } void KisShapeLayerTest::testMergeDown() { testMergeDownImpl(false); } void KisShapeLayerTest::testScaleAndMergeDown() { testMergeDownImpl(true); } namespace { KoPathShape* createSimpleShape(int zIndex) { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(15, 10)); path->lineTo(QPointF(15, 55)); path->lineTo(QPointF(50, 55)); path->lineTo(QPointF(50, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::green))); path->setName(QString("shape_%1").arg(zIndex)); path->setZIndex(zIndex); return path; } } #include "commands/KoShapeReorderCommand.h" #include void testMergingShapeZIndexesImpl(int firstIndexStart, int firstIndexStep, int firstIndexSize, int secondIndexStart, int secondIndexStep, int secondIndexSize) { QList shapesBelow; QList shapesAbove; qDebug() << "Test zIndex merge:"; qDebug() << " " << ppVar(firstIndexStart) << ppVar(firstIndexStep) << ppVar(firstIndexSize); qDebug() << " " << ppVar(secondIndexStart) << ppVar(secondIndexStep) << ppVar(secondIndexSize); for (int i = 0; i < firstIndexSize; i++) { shapesBelow.append(createSimpleShape(firstIndexStart + firstIndexStep * i)); } for (int i = 0; i < secondIndexSize; i++) { shapesAbove.append(createSimpleShape(secondIndexStart + secondIndexStep * i)); } QList shapes = KoShapeReorderCommand::mergeDownShapes(shapesBelow, shapesAbove); KoShapeReorderCommand cmd(shapes); cmd.redo(); for (int i = 0; i < shapesBelow.size(); i++) { if (i > 0 && shapesBelow[i - 1]->zIndex() >= shapesBelow[i]->zIndex()) { qDebug() << ppVar(i); qDebug() << ppVar(shapesBelow[i - 1]->zIndex()) << ppVar(shapesBelow[i]->zIndex()); QFAIL("Shapes have wrong ordering after merge!"); } } if (!shapesBelow.isEmpty() && !shapesAbove.isEmpty()) { if (shapesBelow.last()->zIndex() >= shapesAbove.first()->zIndex()) { qDebug() << ppVar(shapesBelow.last()->zIndex()) << ppVar(shapesAbove.first()->zIndex()); QFAIL("Two shape groups have intersections after merge!"); } } for (int i = 0; i < shapesAbove.size(); i++) { if (i > 0 && shapesAbove[i - 1]->zIndex() >= shapesAbove[i]->zIndex()) { qDebug() << ppVar(i); qDebug() << ppVar(shapesAbove[i - 1]->zIndex()) << ppVar(shapesAbove[i]->zIndex()); QFAIL("Shapes have wrong ordering after merge!"); } } } void KisShapeLayerTest::testMergingShapeZIndexes() { testMergingShapeZIndexesImpl(0, 1, 10, 5, 1, 10); testMergingShapeZIndexesImpl(0, 1, 0, 5, 1, 10); testMergingShapeZIndexesImpl(0, 1, 10, 5, 1, 0); testMergingShapeZIndexesImpl(std::numeric_limits::max() - 10, 1, 10, 5, 1, 10); testMergingShapeZIndexesImpl(-32768, 1024, 64, 0, 1024, 31); testMergingShapeZIndexesImpl(-32768+1023, 1024, 64, 0, 1, 1024); } void KisShapeLayerTest::testCloneScaledLayer() { using namespace TestUtil; ReferenceImageChecker chk("scale_and_clone", "shape_layer_test", ReferenceImageChecker::InternalStorage); chk.setMaxFailingPixels(10); QScopedPointer doc(KisPart::instance()->createDocument()); const QRect refRect(0,0,64,64); MaskParent p(refRect); const qreal resolution = 72.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); KisShapeLayerSP shapeLayer1 = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 150); { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(5, 5)); path->lineTo(QPointF(5, 55)); path->lineTo(QPointF(20, 55)); path->lineTo(QPointF(20, 5)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("shape1"); path->setZIndex(1); shapeLayer1->addShape(path); } p.image->addNode(shapeLayer1); shapeLayer1->setDirty(); p.waitForImageAndShapeLayers(); QCOMPARE(int(p.image->root()->childCount()), 2); chk.checkImage(p.image, "00_initial_layer_update"); { KisFilterStrategy *strategy = new KisBilinearFilterStrategy(); p.image->scaleImage(QSize(32, 32), p.image->xRes(), p.image->yRes(), strategy); p.waitForImageAndShapeLayers(); chk.checkImage(p.image, "01_after_scale_down"); } KisNodeSP clonedLayer = shapeLayer1->clone(); p.image->removeNode(shapeLayer1); p.image->addNode(clonedLayer); clonedLayer->setDirty(); p.waitForImageAndShapeLayers(); QCOMPARE(int(p.image->root()->childCount()), 2); chk.checkImage(p.image, "01_after_scale_down"); QVERIFY(chk.testPassed()); } KISTEST_MAIN(KisShapeLayerTest) diff --git a/libs/ui/tool/KisToolChangesTracker.cpp b/libs/ui/tool/KisToolChangesTracker.cpp new file mode 100644 index 0000000000..2ea8dd0997 --- /dev/null +++ b/libs/ui/tool/KisToolChangesTracker.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018 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 "KisToolChangesTracker.h" + +#include "kis_global.h" +#include + +struct KisToolChangesTracker::Private { + QList undoStack; +}; + + +KisToolChangesTracker::KisToolChangesTracker() + : m_d(new Private) +{ +} + +KisToolChangesTracker::~KisToolChangesTracker() +{ +} + +void KisToolChangesTracker::commitConfig(KisToolChangesTrackerDataSP state) +{ + m_d->undoStack.append(state); +} + +void KisToolChangesTracker::requestUndo() +{ + if (m_d->undoStack.size() > 1) { + m_d->undoStack.removeLast(); + emit sigConfigChanged(m_d->undoStack.last()); + } +} + +KisToolChangesTrackerDataSP KisToolChangesTracker::lastState() const +{ + return !m_d->undoStack.isEmpty() ? m_d->undoStack.last() : 0; +} + +void KisToolChangesTracker::reset() +{ + m_d->undoStack.clear(); +} + +bool KisToolChangesTracker::isEmpty() const +{ + return m_d->undoStack.isEmpty(); +} diff --git a/plugins/extensions/imagesize/imagesize.h b/libs/ui/tool/KisToolChangesTracker.h similarity index 52% copy from plugins/extensions/imagesize/imagesize.h copy to libs/ui/tool/KisToolChangesTracker.h index 892fc333ee..3d4fb5ac5a 100644 --- a/plugins/extensions/imagesize/imagesize.h +++ b/libs/ui/tool/KisToolChangesTracker.h @@ -1,42 +1,49 @@ /* - * imagesize.h -- Part of Krita - * - * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) + * Copyright (c) 2018 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 IMAGESIZE_H -#define IMAGESIZE_H -#include +#ifndef KISTOOLCHANGESTRACKER_H +#define KISTOOLCHANGESTRACKER_H -#include +#include "kritaui_export.h" +#include +#include "KisToolChangesTrackerData.h" -class ImageSize : public KisActionPlugin +class KRITAUI_EXPORT KisToolChangesTracker :public QObject { Q_OBJECT + public: - ImageSize(QObject *parent, const QVariantList &); - ~ImageSize() override; + KisToolChangesTracker(); + ~KisToolChangesTracker(); + + void commitConfig(KisToolChangesTrackerDataSP state); + void requestUndo(); + KisToolChangesTrackerDataSP lastState() const; + void reset(); + + bool isEmpty() const; -private Q_SLOTS: +Q_SIGNALS: + void sigConfigChanged(KisToolChangesTrackerDataSP state); - void slotImageSize(); - void slotCanvasSize(); - void slotLayerSize(); - void slotSelectionScale(); +private: + struct Private; + const QScopedPointer m_d; }; -#endif // IMAGESIZE_H +#endif // KISTOOLCHANGESTRACKER_H diff --git a/plugins/extensions/imagesize/imagesize.h b/libs/ui/tool/KisToolChangesTrackerData.cpp similarity index 59% copy from plugins/extensions/imagesize/imagesize.h copy to libs/ui/tool/KisToolChangesTrackerData.cpp index 892fc333ee..f0c3fc51e8 100644 --- a/plugins/extensions/imagesize/imagesize.h +++ b/libs/ui/tool/KisToolChangesTrackerData.cpp @@ -1,42 +1,36 @@ /* - * imagesize.h -- Part of Krita - * - * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) + * Copyright (c) 2018 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 IMAGESIZE_H -#define IMAGESIZE_H - -#include -#include +#include "KisToolChangesTrackerData.h" -class ImageSize : public KisActionPlugin -{ - Q_OBJECT -public: - ImageSize(QObject *parent, const QVariantList &); - ~ImageSize() override; +struct KisToolChangesTrackerDataSPRegistrar { + KisToolChangesTrackerDataSPRegistrar() { + qRegisterMetaType("KisToolChangesTrackerDataSP"); + } +}; +static KisToolChangesTrackerDataSPRegistrar __registrar; -private Q_SLOTS: - void slotImageSize(); - void slotCanvasSize(); - void slotLayerSize(); - void slotSelectionScale(); -}; +KisToolChangesTrackerData::~KisToolChangesTrackerData() +{ +} -#endif // IMAGESIZE_H +KisToolChangesTrackerData *KisToolChangesTrackerData::clone() const +{ + return new KisToolChangesTrackerData(*this); +} diff --git a/plugins/extensions/imagesize/imagesize.h b/libs/ui/tool/KisToolChangesTrackerData.h similarity index 59% copy from plugins/extensions/imagesize/imagesize.h copy to libs/ui/tool/KisToolChangesTrackerData.h index 892fc333ee..cfcb7d866d 100644 --- a/plugins/extensions/imagesize/imagesize.h +++ b/libs/ui/tool/KisToolChangesTrackerData.h @@ -1,42 +1,37 @@ /* - * imagesize.h -- Part of Krita - * - * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) + * Copyright (c) 2018 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 IMAGESIZE_H -#define IMAGESIZE_H -#include +#ifndef KISTOOLCHANGESTRACKERDATA_H +#define KISTOOLCHANGESTRACKERDATA_H -#include +#include +#include "kritaui_export.h" +#include -class ImageSize : public KisActionPlugin +class KRITAUI_EXPORT KisToolChangesTrackerData { - Q_OBJECT public: - ImageSize(QObject *parent, const QVariantList &); - ~ImageSize() override; + virtual ~KisToolChangesTrackerData(); + virtual KisToolChangesTrackerData* clone() const; +}; -private Q_SLOTS: +typedef QSharedPointer KisToolChangesTrackerDataSP; - void slotImageSize(); - void slotCanvasSize(); - void slotLayerSize(); - void slotSelectionScale(); -}; +Q_DECLARE_METATYPE(KisToolChangesTrackerDataSP) -#endif // IMAGESIZE_H +#endif // KISTOOLCHANGESTRACKERDATA_H diff --git a/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp b/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp index 91ebe4171a..c1f7022eb4 100644 --- a/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp +++ b/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp @@ -1,132 +1,152 @@ /* * 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_selection_tool_config_widget_helper.h" #include #include "kis_selection_options.h" #include "kis_canvas2.h" #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_signals_blocker.h" #include #include KisSelectionToolConfigWidgetHelper::KisSelectionToolConfigWidgetHelper(const QString &windowTitle) : m_optionsWidget(0), m_windowTitle(windowTitle) { } void KisSelectionToolConfigWidgetHelper::createOptionWidget(KisCanvas2 *canvas, const QString &toolId) { m_optionsWidget = new KisSelectionOptions(canvas); Q_CHECK_PTR(m_optionsWidget); m_optionsWidget->setObjectName(toolId + "option widget"); m_optionsWidget->setWindowTitle(m_windowTitle); slotToolActivatedChanged(true); // 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, &KisSelectionOptions::actionChanged, this, &KisSelectionToolConfigWidgetHelper::slotWidgetActionChanged); connect(m_optionsWidget, &KisSelectionOptions::modeChanged, this, &KisSelectionToolConfigWidgetHelper::slotWidgetModeChanged); + connect(m_optionsWidget, &KisSelectionOptions::antiAliasSelectionChanged, + this, &KisSelectionToolConfigWidgetHelper::slotWidgetAntiAliasChanged); + + m_optionsWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_optionsWidget->adjustSize(); } KisSelectionOptions* KisSelectionToolConfigWidgetHelper::optionWidget() const { return m_optionsWidget; } SelectionMode KisSelectionToolConfigWidgetHelper::selectionMode() const { return m_selectionMode; } SelectionAction KisSelectionToolConfigWidgetHelper::selectionAction() const { return m_selectionAction; } +bool KisSelectionToolConfigWidgetHelper::antiAliasSelection() const +{ + return m_antiAliasSelection; +} + void KisSelectionToolConfigWidgetHelper::slotWidgetActionChanged(int action) { if (action >= SELECTION_REPLACE && action <= SELECTION_INTERSECT) { m_selectionAction = (SelectionAction)action; KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase"); cfg.writeEntry("selectionAction", action); + + emit selectionActionChanged(action); } } void KisSelectionToolConfigWidgetHelper::slotWidgetModeChanged(int mode) { m_selectionMode = (SelectionMode)mode; KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase"); cfg.writeEntry("selectionMode", mode); } +void KisSelectionToolConfigWidgetHelper::slotWidgetAntiAliasChanged(bool value) +{ + m_antiAliasSelection = value; + + KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase"); + cfg.writeEntry("antiAliasSelection", value); +} + +void KisSelectionToolConfigWidgetHelper::slotReplaceModeRequested() +{ + m_optionsWidget->setAction(SELECTION_REPLACE); + slotWidgetActionChanged(m_optionsWidget->action()); +} + +void KisSelectionToolConfigWidgetHelper::slotAddModeRequested() +{ + m_optionsWidget->setAction(SELECTION_ADD); + slotWidgetActionChanged(m_optionsWidget->action()); +} + +void KisSelectionToolConfigWidgetHelper::slotSubtractModeRequested() +{ + m_optionsWidget->setAction(SELECTION_SUBTRACT); + slotWidgetActionChanged(m_optionsWidget->action()); +} + +void KisSelectionToolConfigWidgetHelper::slotIntersectModeRequested() +{ + m_optionsWidget->setAction(SELECTION_INTERSECT); + slotWidgetActionChanged(m_optionsWidget->action()); +} + void KisSelectionToolConfigWidgetHelper::slotToolActivatedChanged(bool isActivated) { if (!isActivated) return; KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase"); m_selectionAction = (SelectionAction)cfg.readEntry("selectionAction", (int)SELECTION_REPLACE); m_selectionMode = (SelectionMode)cfg.readEntry("selectionMode", (int)SHAPE_PROTECTION); + m_antiAliasSelection = cfg.readEntry("antiAliasSelection", true); KisSignalsBlocker b(m_optionsWidget); m_optionsWidget->setAction(m_selectionAction); m_optionsWidget->setMode(m_selectionMode); -} - - -bool KisSelectionToolConfigWidgetHelper::processKeyPressEvent(QKeyEvent *event) -{ - event->accept(); - - switch(event->key()) { - case Qt::Key_A: - slotWidgetActionChanged(SELECTION_ADD); - break; - case Qt::Key_S: - slotWidgetActionChanged(SELECTION_SUBTRACT); - break; - case Qt::Key_R: - slotWidgetActionChanged(SELECTION_REPLACE); - break; - case Qt::Key_T: - slotWidgetActionChanged(SELECTION_INTERSECT); - break; - default: - event->ignore(); - } - - return event->isAccepted(); + m_optionsWidget->setAntiAliasSelection(m_antiAliasSelection); } diff --git a/libs/ui/tool/kis_selection_tool_config_widget_helper.h b/libs/ui/tool/kis_selection_tool_config_widget_helper.h index b42d078d05..aff792b269 100644 --- a/libs/ui/tool/kis_selection_tool_config_widget_helper.h +++ b/libs/ui/tool/kis_selection_tool_config_widget_helper.h @@ -1,68 +1,73 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_SELECTION_TOOL_CONFIG_WIDGET_HELPER_H #define __KIS_SELECTION_TOOL_CONFIG_WIDGET_HELPER_H #include #include "kritaui_export.h" #include "kis_selection.h" #include "kis_canvas_resource_provider.h" class QKeyEvent; class KisCanvas2; class KisSelectionOptions; class KoCanvasResourceManager; class KRITAUI_EXPORT KisSelectionToolConfigWidgetHelper : public QObject { Q_OBJECT public: KisSelectionToolConfigWidgetHelper(const QString &windowTitle); void createOptionWidget(KisCanvas2 *canvas, const QString &toolId); KisSelectionOptions* optionWidget() const; SelectionMode selectionMode() const; SelectionAction selectionAction() const; + bool antiAliasSelection() const; int action() const { return selectionAction(); } - bool processKeyPressEvent(QKeyEvent *event); - Q_SIGNALS: void selectionActionChanged(int newAction); - void selectionModeChanged(int newMode); public Q_SLOTS: void slotToolActivatedChanged(bool isActivated); void slotWidgetActionChanged(int action); void slotWidgetModeChanged(int mode); + void slotWidgetAntiAliasChanged(bool value); + + void slotReplaceModeRequested(); + void slotAddModeRequested(); + void slotSubtractModeRequested(); + void slotIntersectModeRequested(); private: KisSelectionOptions* m_optionsWidget; QString m_windowTitle; SelectionMode m_selectionMode; SelectionAction m_selectionAction; + bool m_antiAliasSelection = true; }; #endif /* __KIS_SELECTION_TOOL_CONFIG_WIDGET_HELPER_H */ diff --git a/libs/ui/tool/kis_selection_tool_helper.cpp b/libs/ui/tool/kis_selection_tool_helper.cpp index 198fae9ac7..fa3684dd24 100644 --- a/libs/ui/tool/kis_selection_tool_helper.cpp +++ b/libs/ui/tool/kis_selection_tool_helper.cpp @@ -1,340 +1,340 @@ /* * Copyright (c) 2007 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_selection_tool_helper.h" #include #include #include #include "kis_pixel_selection.h" #include "kis_shape_selection.h" #include "kis_image.h" #include "canvas/kis_canvas2.h" #include "KisViewManager.h" #include "kis_selection_manager.h" #include "kis_transaction.h" #include "commands/kis_selection_commands.h" #include "kis_shape_controller.h" #include #include "kis_processing_applicator.h" -#include "kis_transaction_based_command.h" +#include "commands_new/kis_transaction_based_command.h" #include "kis_gui_context_command.h" #include "kis_command_utils.h" #include "commands/kis_deselect_global_selection_command.h" #include "kis_algebra_2d.h" #include "kis_config.h" #include "kis_action_manager.h" #include "kis_action.h" #include KisSelectionToolHelper::KisSelectionToolHelper(KisCanvas2* canvas, const KUndo2MagicString& name) : m_canvas(canvas) , m_name(name) { m_image = m_canvas->viewManager()->image(); } KisSelectionToolHelper::~KisSelectionToolHelper() { } struct LazyInitGlobalSelection : public KisTransactionBasedCommand { LazyInitGlobalSelection(KisViewManager *view) : m_view(view) {} KisViewManager *m_view; KUndo2Command* paint() override { return !m_view->selection() ? new KisSetEmptyGlobalSelectionCommand(m_view->image()) : 0; } }; void KisSelectionToolHelper::selectPixelSelection(KisPixelSelectionSP selection, SelectionAction action) { KisViewManager* view = m_canvas->viewManager(); if (selection->selectedExactRect().isEmpty()) { m_canvas->viewManager()->selectionManager()->deselect(); return; } KisProcessingApplicator applicator(view->image(), 0 /* we need no automatic updates */, KisProcessingApplicator::SUPPORTS_WRAPAROUND_MODE, KisImageSignalVector() << ModifiedSignal, m_name); applicator.applyCommand(new LazyInitGlobalSelection(view)); struct ApplyToPixelSelection : public KisTransactionBasedCommand { ApplyToPixelSelection(KisViewManager *view, KisPixelSelectionSP selection, SelectionAction action) : m_view(view), m_selection(selection), m_action(action) {} KisViewManager *m_view; KisPixelSelectionSP m_selection; SelectionAction m_action; KUndo2Command* paint() override { KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection(); KIS_ASSERT_RECOVER(pixelSelection) { return 0; } bool hasSelection = !pixelSelection->isEmpty(); KisSelectionTransaction transaction(pixelSelection); if (!hasSelection && m_action == SELECTION_SUBTRACT) { pixelSelection->invert(); } pixelSelection->applySelection(m_selection, m_action); QRect dirtyRect = m_view->image()->bounds(); if (hasSelection && m_action != SELECTION_REPLACE && m_action != SELECTION_INTERSECT) { dirtyRect = m_selection->selectedRect(); } m_view->selection()->updateProjection(dirtyRect); KUndo2Command *savedCommand = transaction.endAndTake(); pixelSelection->setDirty(dirtyRect); if (m_view->selection()->selectedExactRect().isEmpty()) { KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand(); cmd->addCommand(savedCommand); cmd->addCommand(new KisDeselectGlobalSelectionCommand(m_view->image())); savedCommand = cmd; } return savedCommand; } }; applicator.applyCommand(new ApplyToPixelSelection(view, selection, action)); applicator.end(); } void KisSelectionToolHelper::addSelectionShape(KoShape* shape, SelectionAction action) { QList shapes; shapes.append(shape); addSelectionShapes(shapes, action); } void KisSelectionToolHelper::addSelectionShapes(QList< KoShape* > shapes, SelectionAction action) { KisViewManager* view = m_canvas->viewManager(); if (view->image()->wrapAroundModePermitted()) { view->showFloatingMessage( i18n("Shape selection does not fully " "support wraparound mode. Please " "use pixel selection instead"), KisIconUtils::loadIcon("selection-info")); } KisProcessingApplicator applicator(view->image(), 0 /* we need no automatic updates */, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, m_name); applicator.applyCommand(new LazyInitGlobalSelection(view)); struct ClearPixelSelection : public KisTransactionBasedCommand { ClearPixelSelection(KisViewManager *view) : m_view(view) {} KisViewManager *m_view; KUndo2Command* paint() override { KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection(); KIS_ASSERT_RECOVER(pixelSelection) { return 0; } KisSelectionTransaction transaction(pixelSelection); pixelSelection->clear(); return transaction.endAndTake(); } }; if (action == SELECTION_REPLACE || action == SELECTION_DEFAULT) { applicator.applyCommand(new ClearPixelSelection(view)); } struct AddSelectionShape : public KisTransactionBasedCommand { AddSelectionShape(KisViewManager *view, KoShape* shape, SelectionAction action) : m_view(view), m_shape(shape), m_action(action) {} KisViewManager *m_view; KoShape* m_shape; SelectionAction m_action; KUndo2Command* paint() override { KUndo2Command *resultCommand = 0; KisSelectionSP selection = m_view->selection(); if (selection) { KisShapeSelection * shapeSelection = static_cast(selection->shapeSelection()); if (shapeSelection) { QList existingShapes = shapeSelection->shapes(); if (existingShapes.size() == 1) { KoShape *currentShape = existingShapes.first(); QPainterPath path1 = currentShape->absoluteTransformation(0).map(currentShape->outline()); QPainterPath path2 = m_shape->absoluteTransformation(0).map(m_shape->outline()); QPainterPath path = path2; switch (m_action) { case SELECTION_DEFAULT: case SELECTION_REPLACE: path = path2; break; case SELECTION_INTERSECT: path = path1 & path2; break; case SELECTION_ADD: path = path1 | path2; break; case SELECTION_SUBTRACT: path = path1 - path2; break; } KoShape *newShape = KoPathShape::createShapeFromPainterPath(path); newShape->setUserData(new KisShapeSelectionMarker); KUndo2Command *parentCommand = new KUndo2Command(); m_view->canvasBase()->shapeController()->removeShape(currentShape, parentCommand); m_view->canvasBase()->shapeController()->addShape(newShape, 0, parentCommand); resultCommand = parentCommand; } } } if (!resultCommand) { /** * Mark a shape that it belongs to a shape selection */ if(!m_shape->userData()) { m_shape->setUserData(new KisShapeSelectionMarker); } resultCommand = m_view->canvasBase()->shapeController()->addShape(m_shape, 0); } return resultCommand; } }; Q_FOREACH (KoShape* shape, shapes) { applicator.applyCommand( new KisGuiContextCommand(new AddSelectionShape(view, shape, action), view)); } applicator.end(); } bool KisSelectionToolHelper::canShortcutToDeselect(const QRect &rect, SelectionAction action) { return rect.isEmpty() && (action == SELECTION_INTERSECT || action == SELECTION_REPLACE); } bool KisSelectionToolHelper::canShortcutToNoop(const QRect &rect, SelectionAction action) { return rect.isEmpty() && action == SELECTION_ADD; } bool KisSelectionToolHelper::tryDeselectCurrentSelection(const QRectF selectionViewRect, SelectionAction action) { bool result = false; if (KisAlgebra2D::maxDimension(selectionViewRect) < KisConfig(true).selectionViewSizeMinimum() && (action == SELECTION_INTERSECT || action == SELECTION_REPLACE)) { // Queueing this action to ensure we avoid a race condition when unlocking the node system QTimer::singleShot(0, m_canvas->viewManager()->selectionManager(), SLOT(deselect())); result = true; } return result; } QMenu* KisSelectionToolHelper::getSelectionContextMenu(KisCanvas2* canvas) { QMenu *m_contextMenu = new QMenu(); KisActionManager * actionMan = canvas->viewManager()->actionManager(); m_contextMenu->addAction(actionMan->actionByName("deselect")); m_contextMenu->addAction(actionMan->actionByName("invert")); m_contextMenu->addAction(actionMan->actionByName("select_all")); m_contextMenu->addSeparator(); m_contextMenu->addAction(actionMan->actionByName("cut_selection_to_new_layer")); m_contextMenu->addAction(actionMan->actionByName("copy_selection_to_new_layer")); m_contextMenu->addSeparator(); KisSelectionSP selection = canvas->viewManager()->selection(); if (selection && canvas->viewManager()->selectionEditable()) { m_contextMenu->addAction(actionMan->actionByName("edit_selection")); if (!selection->hasShapeSelection()) { m_contextMenu->addAction(actionMan->actionByName("convert_to_vector_selection")); } else { m_contextMenu->addAction(actionMan->actionByName("convert_to_raster_selection")); } QMenu *transformMenu = m_contextMenu->addMenu(i18n("Transform")); transformMenu->addAction(actionMan->actionByName("selectionscale")); transformMenu->addAction(actionMan->actionByName("growselection")); transformMenu->addAction(actionMan->actionByName("shrinkselection")); transformMenu->addAction(actionMan->actionByName("borderselection")); transformMenu->addAction(actionMan->actionByName("smoothselection")); transformMenu->addAction(actionMan->actionByName("featherselection")); transformMenu->addAction(actionMan->actionByName("stroke_selection")); m_contextMenu->addSeparator(); } m_contextMenu->addAction(actionMan->actionByName("resizeimagetoselection")); m_contextMenu->addSeparator(); m_contextMenu->addAction(actionMan->actionByName("toggle_display_selection")); m_contextMenu->addAction(actionMan->actionByName("show-global-selection-mask")); return m_contextMenu; } diff --git a/libs/ui/tool/kis_stabilized_events_sampler.h b/libs/ui/tool/kis_stabilized_events_sampler.h index 20a7280e95..8331665258 100644 --- a/libs/ui/tool/kis_stabilized_events_sampler.h +++ b/libs/ui/tool/kis_stabilized_events_sampler.h @@ -1,87 +1,87 @@ /* * 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_STABILIZED_EVENTS_SAMPLER_H #define __KIS_STABILIZED_EVENTS_SAMPLER_H #include #include #include "kritaui_export.h" class KisPaintInformation; #include class KRITAUI_EXPORT KisStabilizedEventsSampler { public: KisStabilizedEventsSampler(int sampleTime = 1); ~KisStabilizedEventsSampler(); void clear(); void addEvent(const KisPaintInformation &pi); void addFinishingEvent(int numSamples); public: - class iterator : + class KRITAUI_EXPORT iterator : public boost::iterator_facade { public: iterator() : m_sampler(0), m_index(0), m_alpha(0) {} iterator(const KisStabilizedEventsSampler* sampler, int index, qreal alpha) : m_sampler(sampler), m_index(index), m_alpha(alpha) {} private: friend class boost::iterator_core_access; void increment() { m_index++; } bool equal(iterator const& other) const { return m_index == other.m_index && m_sampler == other.m_sampler; } const KisPaintInformation& dereference() const; private: const KisStabilizedEventsSampler* m_sampler; int m_index; qreal m_alpha; }; std::pair range() const; private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_STABILIZED_EVENTS_SAMPLER_H */ diff --git a/libs/ui/tool/kis_tool_select_base.h b/libs/ui/tool/kis_tool_select_base.h index 56181368e1..85eefc1a4e 100644 --- a/libs/ui/tool/kis_tool_select_base.h +++ b/libs/ui/tool/kis_tool_select_base.h @@ -1,331 +1,419 @@ /* This file is part of the KDE project * Copyright (C) 2009 Boudewijn Rempt * 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 KISTOOLSELECTBASE_H #define KISTOOLSELECTBASE_H #include "KoPointerEvent.h" #include "kis_tool.h" #include "kis_canvas2.h" #include "kis_selection.h" #include "kis_selection_options.h" #include "kis_selection_tool_config_widget_helper.h" #include "KisViewManager.h" #include "kis_selection_manager.h" #include "kis_selection_modifier_mapper.h" #include "strokes/move_stroke_strategy.h" #include "kis_image.h" #include "kis_cursor.h" +#include "kis_action_manager.h" +#include "kis_action.h" +#include "kis_signal_auto_connection.h" +#include "kis_selection_tool_helper.h" /** * This is a basic template to create selection tools from basic path based drawing tools. * The template overrides the ability to execute alternate actions correctly. * The default behavior for the modifier keys is as follows: * * Shift: add to selection * Alt: subtract from selection * Shift+Alt: intersect current selection * Ctrl: replace selection * * The mapping itself is done in KisSelectionModifierMapper. * * Certain tools also use modifier keys to alter their behavior, e.g. forcing square proportions with the rectangle tool. * The template enables the following rules for forwarding keys: * 1) Any modifier keys held *when the tool is first activated* will determine * the new selection method. This is recorded in m_selectionActionAlternate. A * value of m_selectionActionAlternate = SELECTION_DEFAULT means no modifier was * being pressed when the tool was activated. * * 2) If the underlying tool *does not take modifier keys*, pressing modifier * keys in the middle of a stroke will change the selection method. This is * recorded in m_selectionAction. A value of SELECTION_DEFAULT means no modifier * is being pressed. Applies to the lasso tool and polygon tool. * * 3) If the underlying tool *takes modifier keys,* they will always be * forwarded to the underlying tool, and it is not possible to change the * selection method in the middle of a stroke. */ template class KisToolSelectBase : public BaseClass { public: KisToolSelectBase(KoCanvasBase* canvas, const QString toolName) :BaseClass(canvas), m_widgetHelper(toolName), - m_selectionAction(SELECTION_DEFAULT), m_selectionActionAlternate(SELECTION_DEFAULT) { KisSelectionModifierMapper::instance(); + initShortcuts(); } KisToolSelectBase(KoCanvasBase* canvas, const QCursor cursor, const QString toolName) :BaseClass(canvas, cursor), m_widgetHelper(toolName), - m_selectionAction(SELECTION_DEFAULT), m_selectionActionAlternate(SELECTION_DEFAULT) { + KisSelectionModifierMapper::instance(); + initShortcuts(); } KisToolSelectBase(KoCanvasBase* canvas, QCursor cursor, QString toolName, KisTool *delegateTool) :BaseClass(canvas, cursor, delegateTool), m_widgetHelper(toolName), - m_selectionAction(SELECTION_DEFAULT), m_selectionActionAlternate(SELECTION_DEFAULT) { + KisSelectionModifierMapper::instance(); + initShortcuts(); + } + + void initShortcuts() + { + KisCanvas2 * kiscanvas = static_cast(canvas()); + KisViewManager* viewManager = kiscanvas->viewManager(); + KisActionManager *manager = viewManager->actionManager(); + + KisAction *action = 0; + + action = manager->createAction("selection_tool_mode_add"); + this->addAction(action->objectName(), action); + + action = manager->createAction("selection_tool_mode_replace"); + this->addAction(action->objectName(), action); + + action = manager->createAction("selection_tool_mode_subtract"); + this->addAction(action->objectName(), action); + + action = manager->createAction("selection_tool_mode_intersect"); + this->addAction(action->objectName(), action); + } + + void updateActionShortcutToolTips() { + KisSelectionOptions *widget = m_widgetHelper.optionWidget(); + if (widget) { + widget->updateActionButtonToolTip( + SELECTION_REPLACE, + this->action("selection_tool_mode_replace")->shortcut()); + widget->updateActionButtonToolTip( + SELECTION_ADD, + this->action("selection_tool_mode_add")->shortcut()); + widget->updateActionButtonToolTip( + SELECTION_SUBTRACT, + this->action("selection_tool_mode_subtract")->shortcut()); + widget->updateActionButtonToolTip( + SELECTION_INTERSECT, + this->action("selection_tool_mode_intersect")->shortcut()); + } + } + + void activate(KoToolBase::ToolActivation activation, const QSet &shapes) + { + BaseClass::activate(activation, shapes); + + m_modeConnections.addUniqueConnection( + this->action("selection_tool_mode_replace"), SIGNAL(triggered()), + &m_widgetHelper, SLOT(slotReplaceModeRequested())); + + m_modeConnections.addUniqueConnection( + this->action("selection_tool_mode_add"), SIGNAL(triggered()), + &m_widgetHelper, SLOT(slotAddModeRequested())); + + m_modeConnections.addUniqueConnection( + this->action("selection_tool_mode_subtract"), SIGNAL(triggered()), + &m_widgetHelper, SLOT(slotSubtractModeRequested())); + + m_modeConnections.addUniqueConnection( + this->action("selection_tool_mode_intersect"), SIGNAL(triggered()), + &m_widgetHelper, SLOT(slotIntersectModeRequested())); + + updateActionShortcutToolTips(); + } + + void deactivate() + { + BaseClass::deactivate(); + m_modeConnections.clear(); } QWidget* createOptionWidget() { KisCanvas2* canvas = dynamic_cast(this->canvas()); Q_ASSERT(canvas); m_widgetHelper.createOptionWidget(canvas, this->toolId()); this->connect(this, SIGNAL(isActiveChanged(bool)), &m_widgetHelper, SLOT(slotToolActivatedChanged(bool))); - return m_widgetHelper.optionWidget(); - } + this->connect(&m_widgetHelper, SIGNAL(selectionActionChanged(int)), this, SLOT(resetCursorStyle())); - void keyPressEvent(QKeyEvent *event) - { - if (!m_widgetHelper.processKeyPressEvent(event)) { - BaseClass::keyPressEvent(event); - } + updateActionShortcutToolTips(); + + return m_widgetHelper.optionWidget(); } SelectionMode selectionMode() const { return m_widgetHelper.selectionMode(); } SelectionAction selectionAction() const { if (alternateSelectionAction() == SELECTION_DEFAULT) { return m_widgetHelper.selectionAction(); } return alternateSelectionAction(); } bool antiAliasSelection() const { - return m_widgetHelper.optionWidget()->antiAliasSelection(); + return m_widgetHelper.antiAliasSelection(); } SelectionAction alternateSelectionAction() const { return m_selectionActionAlternate; } KisSelectionOptions* selectionOptionWidget() { return m_widgetHelper.optionWidget(); } virtual void setAlternateSelectionAction(SelectionAction action) { m_selectionActionAlternate = action; dbgKrita << "Changing to selection action" << m_selectionActionAlternate; } void activateAlternateAction(KisTool::AlternateAction action) { Q_UNUSED(action); BaseClass::activatePrimaryAction(); } void deactivateAlternateAction(KisTool::AlternateAction action) { Q_UNUSED(action); BaseClass::deactivatePrimaryAction(); } void beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { Q_UNUSED(action); beginPrimaryAction(event); } void continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { Q_UNUSED(action); continuePrimaryAction(event); } void endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { Q_UNUSED(action); endPrimaryAction(event); } KisNodeSP locateSelectionMaskUnderCursor(const QPointF &pos, Qt::KeyboardModifiers modifiers) { if (modifiers != Qt::NoModifier) return 0; KisCanvas2* canvas = dynamic_cast(this->canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas, 0); KisSelectionSP selection = canvas->viewManager()->selection(); if (selection && selection->outlineCacheValid() && selection->outlineCache().contains(pos)) { KisNodeSP parent = selection->parentNode(); if (parent && parent->isEditable()) { return parent; } } return 0; } + void keyPressEvent(QKeyEvent *event) { + if (this->mode() != KisTool::PAINT_MODE) { + setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers())); + this->resetCursorStyle(); + } + BaseClass::keyPressEvent(event); + } + + void keyReleaseEvent(QKeyEvent *event) { + if (this->mode() != KisTool::PAINT_MODE) { + setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers())); + this->resetCursorStyle(); + } + BaseClass::keyPressEvent(event); + } + void mouseMoveEvent(KoPointerEvent *event) { - if (!this->hasUserInteractionRunning()) { + if (!this->hasUserInteractionRunning() && + (m_moveStrokeId || this->mode() != KisTool::PAINT_MODE)) { + const QPointF pos = this->convertToPixelCoord(event->point); KisNodeSP selectionMask = locateSelectionMaskUnderCursor(pos, event->modifiers()); if (selectionMask) { this->useCursor(KisCursor::moveCursor()); } else { + setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers())); this->resetCursorStyle(); } } BaseClass::mouseMoveEvent(event); } virtual void beginPrimaryAction(KoPointerEvent *event) { if (!this->hasUserInteractionRunning()) { const QPointF pos = this->convertToPixelCoord(event->point); KisCanvas2* canvas = dynamic_cast(this->canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN(canvas); KisNodeSP selectionMask = locateSelectionMaskUnderCursor(pos, event->modifiers()); if (selectionMask) { KisStrokeStrategy *strategy = new MoveStrokeStrategy({selectionMask}, this->image().data(), this->image().data()); m_moveStrokeId = this->image()->startStroke(strategy); m_dragStartPos = pos; return; } } keysAtStart = event->modifiers(); setAlternateSelectionAction(KisSelectionModifierMapper::map(keysAtStart)); if (alternateSelectionAction() != SELECTION_DEFAULT) { BaseClass::listenToModifiers(false); } BaseClass::beginPrimaryAction(event); } virtual void continuePrimaryAction(KoPointerEvent *event) { if (m_moveStrokeId) { const QPointF pos = this->convertToPixelCoord(event->point); const QPoint offset((pos - m_dragStartPos).toPoint()); this->image()->addJob(m_moveStrokeId, new MoveStrokeStrategy::Data(offset)); return; } //If modifier keys have changed, tell the base tool it can start capturing modifiers if ((keysAtStart != event->modifiers()) && !BaseClass::listeningToModifiers()) { BaseClass::listenToModifiers(true); } //Always defer to the base class if it signals it is capturing modifier keys if (!BaseClass::listeningToModifiers()) { setAlternateSelectionAction(KisSelectionModifierMapper::map(event->modifiers())); } BaseClass::continuePrimaryAction(event); } void endPrimaryAction(KoPointerEvent *event) { if (m_moveStrokeId) { this->image()->endStroke(m_moveStrokeId); m_moveStrokeId.clear(); return; } keysAtStart = Qt::NoModifier; //reset this with each action BaseClass::endPrimaryAction(event); } - void changeSelectionAction(int newSelectionAction) - { - // Simple sanity check - if(newSelectionAction >= SELECTION_REPLACE && - newSelectionAction <= SELECTION_INTERSECT && - m_selectionAction != newSelectionAction) - { - m_selectionAction = (SelectionAction)newSelectionAction; - } - } - bool selectionDragInProgress() const { return m_moveStrokeId; } + QMenu* popupActionsMenu() { + KisCanvas2 * kisCanvas = dynamic_cast(canvas()); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(kisCanvas, 0); + + return KisSelectionToolHelper::getSelectionContextMenu(kisCanvas); + } + + protected: using BaseClass::canvas; KisSelectionToolConfigWidgetHelper m_widgetHelper; - SelectionAction m_selectionAction; SelectionAction m_selectionActionAlternate; private: Qt::KeyboardModifiers keysAtStart; QPointF m_dragStartPos; KisStrokeId m_moveStrokeId; + + KisSignalAutoConnectionsStore m_modeConnections; }; struct FakeBaseTool : KisTool { FakeBaseTool(KoCanvasBase* canvas) : KisTool(canvas, QCursor()) { } FakeBaseTool(KoCanvasBase* canvas, const QString &toolName) : KisTool(canvas, QCursor()) { Q_UNUSED(toolName); } FakeBaseTool(KoCanvasBase* canvas, const QCursor &cursor) : KisTool(canvas, cursor) { } bool hasUserInteractionRunning() const { return false; } }; typedef KisToolSelectBase KisToolSelect; #endif // KISTOOLSELECTBASE_H diff --git a/libs/ui/widgets/KisNewsWidget.cpp b/libs/ui/widgets/KisNewsWidget.cpp new file mode 100644 index 0000000000..5a2cd2a5c0 --- /dev/null +++ b/libs/ui/widgets/KisNewsWidget.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2018 boud + * + * 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 "KisNewsWidget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "kis_config.h" +#include "KisMultiFeedRSSModel.h" + + +KisNewsDelegate::KisNewsDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ + qDebug() << "Delegate created"; +} + +void KisNewsDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + painter->save(); + + QStyleOptionViewItem optionCopy = option; + initStyleOption(&optionCopy, index); + + QStyle *style = optionCopy.widget? optionCopy.widget->style() : QApplication::style(); + + QTextDocument doc; + doc.setHtml(optionCopy.text); + doc.setDocumentMargin(10); + + /// Painting item without text + optionCopy.text = QString(); + style->drawControl(QStyle::CE_ItemViewItem, &optionCopy, painter); + + QAbstractTextDocumentLayout::PaintContext ctx; + + // Highlighting text if item is selected + if (optionCopy.state & QStyle::State_Selected) { + ctx.palette.setColor(QPalette::Text, optionCopy.palette.color(QPalette::Active, QPalette::HighlightedText)); + } + + painter->translate(optionCopy.rect.left(), optionCopy.rect.top()); + QRect clip(0, 0, optionCopy.rect.width(), optionCopy.rect.height()); + doc.setPageSize(clip.size()); + doc.drawContents(painter, clip); + painter->restore(); +} + +QSize KisNewsDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem optionCopy = option; + initStyleOption(&optionCopy, index); + + QTextDocument doc; + doc.setHtml(optionCopy.text); + doc.setTextWidth(optionCopy.rect.width()); + return QSize(doc.idealWidth(), doc.size().height()); +} + +KisNewsWidget::KisNewsWidget(QWidget *parent) + : QWidget(parent) +{ + setupUi(this); + m_rssModel = new MultiFeedRssModel(this); + + setCursor(Qt::PointingHandCursor); + + listNews->setModel(m_rssModel); + listNews->setItemDelegate(new KisNewsDelegate(listNews)); + connect(listNews, SIGNAL(clicked(const QModelIndex&)), this, SLOT(itemSelected(const QModelIndex&))); +} + +void KisNewsWidget::toggleNews(bool toggle) +{ + KisConfig cfg(false); + cfg.writeEntry("FetchNews", toggle); + + if (toggle) { + m_rssModel->addFeed(QLatin1String("https://krita.org/en/feed/")); + } + else { + m_rssModel->removeFeed(QLatin1String("https://krita.org/en/feed/")); + } +} + +void KisNewsWidget::itemSelected(const QModelIndex &idx) +{ + if (idx.isValid()) { + QString link = idx.data(RssRoles::LinkRole).toString(); + QDesktopServices::openUrl(QUrl(link)); + } +} diff --git a/libs/ui/widgets/KisNewsWidget.h b/libs/ui/widgets/KisNewsWidget.h new file mode 100644 index 0000000000..4dda2f4512 --- /dev/null +++ b/libs/ui/widgets/KisNewsWidget.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 boud + * + * 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 KISNEWSWIDGET_H +#define KISNEWSWIDGET_H + +#include +#include +#include + +#include + +class MultiFeedRssModel; + +class KisNewsDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + KisNewsDelegate(QObject *parent = 0); + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + +/** + * @brief The KisNewsWidget class shows the latest news from Krita.org + */ +class KisNewsWidget : public QWidget, public Ui::KisNewsPage +{ + Q_OBJECT +public: + explicit KisNewsWidget(QWidget *parent = nullptr); + + + +private Q_SLOTS: + void toggleNews(bool toggle); + void itemSelected(const QModelIndex &idx); +private: + bool m_getNews {false}; + MultiFeedRssModel *m_rssModel {0}; +}; + +#endif // KISNEWSWIDGET_H diff --git a/libs/ui/widgets/kis_filter_selector_widget.cc b/libs/ui/widgets/kis_filter_selector_widget.cc index 56f3db014b..9a8c7d581a 100644 --- a/libs/ui/widgets/kis_filter_selector_widget.cc +++ b/libs/ui/widgets/kis_filter_selector_widget.cc @@ -1,351 +1,353 @@ /* * Copyright (c) 2007-2008 Cyrille Berger * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_filter_selector_widget.h" #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgfilterselector.h" #include #include #include #include #include #include "kis_default_bounds.h" // From krita/ui #include "kis_bookmarked_configurations_editor.h" #include "kis_bookmarked_filter_configurations_model.h" #include "kis_filters_model.h" #include "kis_config.h" class ThumbnailBounds : public KisDefaultBounds { public: ThumbnailBounds() : KisDefaultBounds() {} ~ThumbnailBounds() override {} QRect bounds() const override { return QRect(0, 0, 100, 100); } private: Q_DISABLE_COPY(ThumbnailBounds) }; struct KisFilterSelectorWidget::Private { - QWidget* currentCentralWidget; - KisConfigWidget* currentFilterConfigurationWidget; + QWidget *currentCentralWidget {0}; + KisConfigWidget *currentFilterConfigurationWidget {0}; KisFilterSP currentFilter; KisPaintDeviceSP paintDevice; Ui_FilterSelector uiFilterSelector; KisPaintDeviceSP thumb; - KisBookmarkedFilterConfigurationsModel* currentBookmarkedFilterConfigurationsModel; - KisFiltersModel* filtersModel; - QGridLayout *widgetLayout; - KisViewManager *view; - bool showFilterGallery; + KisBookmarkedFilterConfigurationsModel *currentBookmarkedFilterConfigurationsModel {0}; + KisFiltersModel *filtersModel {}; + QGridLayout *widgetLayout {}; + KisViewManager *view{}; + bool showFilterGallery {true}; }; KisFilterSelectorWidget::KisFilterSelectorWidget(QWidget* parent) : d(new Private) { Q_UNUSED(parent); setObjectName("KisFilterSelectorWidget"); - d->currentCentralWidget = 0; - d->currentFilterConfigurationWidget = 0; - d->currentBookmarkedFilterConfigurationsModel = 0; - d->currentFilter = 0; - d->filtersModel = 0; - d->view = 0; - d->showFilterGallery = true; d->uiFilterSelector.setupUi(this); d->widgetLayout = new QGridLayout(d->uiFilterSelector.centralWidgetHolder); d->widgetLayout->setContentsMargins(0,0,0,0); d->widgetLayout->setHorizontalSpacing(0); showFilterGallery(false); connect(d->uiFilterSelector.filtersSelector, SIGNAL(clicked(const QModelIndex&)), SLOT(setFilterIndex(const QModelIndex &))); connect(d->uiFilterSelector.filtersSelector, SIGNAL(activated(const QModelIndex&)), SLOT(setFilterIndex(const QModelIndex &))); - connect(d->uiFilterSelector.comboBoxPresets, SIGNAL(activated(int)), - SLOT(slotBookmarkedFilterConfigurationSelected(int))); + connect(d->uiFilterSelector.comboBoxPresets, SIGNAL(activated(int)),SLOT(slotBookmarkedFilterConfigurationSelected(int))); connect(d->uiFilterSelector.pushButtonEditPressets, SIGNAL(pressed()), SLOT(editConfigurations())); connect(d->uiFilterSelector.btnXML, SIGNAL(clicked()), this, SLOT(showXMLdialog())); + } KisFilterSelectorWidget::~KisFilterSelectorWidget() { delete d->filtersModel; delete d->currentBookmarkedFilterConfigurationsModel; delete d->currentCentralWidget; delete d->widgetLayout; delete d; } void KisFilterSelectorWidget::setView(KisViewManager *view) { d->view = view; } void KisFilterSelectorWidget::setPaintDevice(bool showAll, KisPaintDeviceSP _paintDevice) { if (!_paintDevice) return; if (d->filtersModel) delete d->filtersModel; d->paintDevice = _paintDevice; d->thumb = d->paintDevice->createThumbnailDevice(100, 100); d->thumb->setDefaultBounds(new ThumbnailBounds()); d->filtersModel = new KisFiltersModel(showAll, d->thumb); d->uiFilterSelector.filtersSelector->setFilterModel(d->filtersModel); d->uiFilterSelector.filtersSelector->header()->setVisible(false); KisConfig cfg(true); QModelIndex idx = d->filtersModel->indexForFilter(cfg.readEntry("FilterSelector/LastUsedFilter", "levels")); if (!idx.isValid()) { idx = d->filtersModel->indexForFilter("levels"); } if (isFilterGalleryVisible()) { d->uiFilterSelector.filtersSelector->activateFilter(idx); } } void KisFilterSelectorWidget::showFilterGallery(bool visible) { if (d->showFilterGallery == visible) { return; } d->showFilterGallery = visible; update(); emit sigFilterGalleryToggled(visible); emit sigSizeChanged(); } void KisFilterSelectorWidget::showXMLdialog() { if (currentFilter()->showConfigurationWidget()) { QDialog *xmlDialog = new QDialog(); xmlDialog->setMinimumWidth(500); xmlDialog->setWindowTitle(i18n("Filter configuration XML")); QVBoxLayout *xmllayout = new QVBoxLayout(xmlDialog); QPlainTextEdit *text = new QPlainTextEdit(xmlDialog); KisFilterConfigurationSP config = configuration(); text->setPlainText(config->toXML()); xmllayout->addWidget(text); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, xmlDialog); connect(buttons, SIGNAL(accepted()), xmlDialog, SLOT(accept())); connect(buttons, SIGNAL(rejected()), xmlDialog, SLOT(reject())); xmllayout->addWidget(buttons); if (xmlDialog->exec()==QDialog::Accepted) { QDomDocument doc; doc.setContent(text->toPlainText()); config->fromXML(doc.documentElement()); if (config) { d->currentFilterConfigurationWidget->setConfiguration(config); } } } } bool KisFilterSelectorWidget::isFilterGalleryVisible() const { return d->showFilterGallery; } KisFilterSP KisFilterSelectorWidget::currentFilter() const { return d->currentFilter; } void KisFilterSelectorWidget::setFilter(KisFilterSP f) { Q_ASSERT(f); Q_ASSERT(d->filtersModel); setWindowTitle(f->name()); dbgKrita << "setFilter: " << f; d->currentFilter = f; delete d->currentCentralWidget; { bool v = d->uiFilterSelector.filtersSelector->blockSignals(true); d->uiFilterSelector.filtersSelector->setCurrentIndex(d->filtersModel->indexForFilter(f->id())); d->uiFilterSelector.filtersSelector->blockSignals(v); } KisConfigWidget* widget = d->currentFilter->createConfigurationWidget(d->uiFilterSelector.centralWidgetHolder, d->paintDevice); if (!widget) { // No widget, so display a label instead d->uiFilterSelector.comboBoxPresets->setEnabled(false); d->uiFilterSelector.pushButtonEditPressets->setEnabled(false); d->uiFilterSelector.btnXML->setEnabled(false); d->currentFilterConfigurationWidget = 0; d->currentCentralWidget = new QLabel(i18n("No configuration options"), d->uiFilterSelector.centralWidgetHolder); d->uiFilterSelector.scrollArea->setMinimumSize(d->currentCentralWidget->sizeHint()); qobject_cast(d->currentCentralWidget)->setAlignment(Qt::AlignCenter); } else { d->uiFilterSelector.comboBoxPresets->setEnabled(true); d->uiFilterSelector.pushButtonEditPressets->setEnabled(true); d->uiFilterSelector.btnXML->setEnabled(true); d->currentFilterConfigurationWidget = widget; d->currentCentralWidget = widget; widget->layout()->setContentsMargins(0,0,0,0); d->currentFilterConfigurationWidget->setView(d->view); d->currentFilterConfigurationWidget->blockSignals(true); d->currentFilterConfigurationWidget->setConfiguration(d->currentFilter->defaultConfiguration()); d->currentFilterConfigurationWidget->blockSignals(false); d->uiFilterSelector.scrollArea->setContentsMargins(0,0,0,0); d->uiFilterSelector.scrollArea->setMinimumWidth(widget->sizeHint().width() + 18); connect(d->currentFilterConfigurationWidget, SIGNAL(sigConfigurationUpdated()), this, SIGNAL(configurationChanged())); } // Change the list of presets delete d->currentBookmarkedFilterConfigurationsModel; d->currentBookmarkedFilterConfigurationsModel = new KisBookmarkedFilterConfigurationsModel(d->thumb, f); d->uiFilterSelector.comboBoxPresets->setModel(d->currentBookmarkedFilterConfigurationsModel); // Add the widget to the layout d->currentCentralWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); d->widgetLayout->addWidget(d->currentCentralWidget, 0 , 0); + int lastBookmarkedFilterConfiguration = KisConfig(true).readEntry("lastBookmarkedFilterConfiguration/" + f->id(), 0); + if (d->uiFilterSelector.comboBoxPresets->count() > lastBookmarkedFilterConfiguration) { + d->uiFilterSelector.comboBoxPresets->setCurrentIndex(lastBookmarkedFilterConfiguration); + slotBookmarkedFilterConfigurationSelected(lastBookmarkedFilterConfiguration); + } + update(); } void KisFilterSelectorWidget::setFilterIndex(const QModelIndex& idx) { if (!idx.isValid()) return; Q_ASSERT(d->filtersModel); KisFilter* filter = const_cast(d->filtersModel->indexToFilter(idx)); if (filter) { setFilter(filter); } else { if (d->currentFilter) { bool v = d->uiFilterSelector.filtersSelector->blockSignals(true); QModelIndex idx = d->filtersModel->indexForFilter(d->currentFilter->id()); d->uiFilterSelector.filtersSelector->setCurrentIndex(idx); d->uiFilterSelector.filtersSelector->scrollTo(idx); d->uiFilterSelector.filtersSelector->blockSignals(v); } } KisConfig cfg(false); cfg.writeEntry("FilterSelector/LastUsedFilter", d->currentFilter->id()); emit(configurationChanged()); } void KisFilterSelectorWidget::slotBookmarkedFilterConfigurationSelected(int index) { if (d->currentFilterConfigurationWidget) { QModelIndex modelIndex = d->currentBookmarkedFilterConfigurationsModel->index(index, 0); KisFilterConfigurationSP config = d->currentBookmarkedFilterConfigurationsModel->configuration(modelIndex); d->currentFilterConfigurationWidget->setConfiguration(config); + if (d->currentFilter && index != KisConfig(true).readEntry("lastBookmarkedFilterConfiguration/" + d->currentFilter->id(), 0)) { + KisConfig(false).writeEntry("lastBookmarkedFilterConfiguration/" + d->currentFilter->id(), index); + } } } void KisFilterSelectorWidget::editConfigurations() { KisSerializableConfigurationSP config = d->currentFilterConfigurationWidget ? d->currentFilterConfigurationWidget->configuration() : 0; KisBookmarkedConfigurationsEditor editor(this, d->currentBookmarkedFilterConfigurationsModel, config); editor.exec(); } void KisFilterSelectorWidget::update() { d->uiFilterSelector.filtersSelector->setVisible(d->showFilterGallery); if (d->showFilterGallery) { setMinimumWidth(qMax(sizeHint().width(), 700)); d->uiFilterSelector.scrollArea->setMinimumHeight(400); setMinimumHeight(d->uiFilterSelector.widget->sizeHint().height()); if (d->currentFilter) { bool v = d->uiFilterSelector.filtersSelector->blockSignals(true); d->uiFilterSelector.filtersSelector->setCurrentIndex(d->filtersModel->indexForFilter(d->currentFilter->id())); d->uiFilterSelector.filtersSelector->blockSignals(v); } } else { if (d->currentCentralWidget) { d->uiFilterSelector.scrollArea->setMinimumHeight(qMin(400, d->currentCentralWidget->sizeHint().height())); } setMinimumSize(d->uiFilterSelector.widget->sizeHint()); } } KisFilterConfigurationSP KisFilterSelectorWidget::configuration() { if (d->currentFilterConfigurationWidget) { KisFilterConfigurationSP config = dynamic_cast(d->currentFilterConfigurationWidget->configuration().data()); if (config) { return config; } } else if (d->currentFilter) { return d->currentFilter->defaultConfiguration(); } return 0; } void KisFilterTree::setFilterModel(QAbstractItemModel *model) { m_model = model; } void KisFilterTree::activateFilter(QModelIndex idx) { setModel(m_model); selectionModel()->select(idx, QItemSelectionModel::SelectCurrent); expand(idx); scrollTo(idx); emit activated(idx); } void KisFilterSelectorWidget::setVisible(bool visible) { QWidget::setVisible(visible); if (visible) { update(); } } diff --git a/libs/ui/widgets/kis_meta_data_merge_strategy_chooser_widget.cc b/libs/ui/widgets/kis_meta_data_merge_strategy_chooser_widget.cc index 794524ec52..eb35e62f91 100644 --- a/libs/ui/widgets/kis_meta_data_merge_strategy_chooser_widget.cc +++ b/libs/ui/widgets/kis_meta_data_merge_strategy_chooser_widget.cc @@ -1,88 +1,88 @@ /* * Copyright (c) 2008 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 "widgets/kis_meta_data_merge_strategy_chooser_widget.h" #include #include -#include +#include #include "ui_wdgmetadatamergestrategychooser.h" struct KisMetaDataMergeStrategyChooserWidget::Private { Ui::WdgMetaDataMergeStrategyChooser uiWdg; }; KisMetaDataMergeStrategyChooserWidget::KisMetaDataMergeStrategyChooserWidget(QWidget* parent) : d(new Private) { Q_UNUSED(parent); setObjectName("KisMetadataMergeStrategyChooserWidget"); d->uiWdg.setupUi(this); QList keys = KisMetaData::MergeStrategyRegistry::instance()->keys(); Q_FOREACH (const QString & key, keys) { const KisMetaData::MergeStrategy* ms = KisMetaData::MergeStrategyRegistry::instance()->get(key); d->uiWdg.mergeStrategy->addItem(ms->name(), ms->id()); } int initial = d->uiWdg.mergeStrategy->findData("Smart"); if (initial != -1) { d->uiWdg.mergeStrategy->setCurrentIndex(initial); } setCurrentDescription(d->uiWdg.mergeStrategy->currentIndex()); connect(d->uiWdg.mergeStrategy, SIGNAL(currentIndexChanged(int)), SLOT(setCurrentDescription(int))); } KisMetaDataMergeStrategyChooserWidget::~KisMetaDataMergeStrategyChooserWidget() { delete d; } const KisMetaData::MergeStrategy* KisMetaDataMergeStrategyChooserWidget::currentStrategy() { return mergeStrategy(d->uiWdg.mergeStrategy->currentIndex()); } void KisMetaDataMergeStrategyChooserWidget::setCurrentDescription(int index) { d->uiWdg.description->setText(mergeStrategy(index)->description()); } const KisMetaData::MergeStrategy* KisMetaDataMergeStrategyChooserWidget::mergeStrategy(int index) { return KisMetaData::MergeStrategyRegistry::instance()->get( d->uiWdg.mergeStrategy->itemData(index).toString()); } const KisMetaData::MergeStrategy* KisMetaDataMergeStrategyChooserWidget::showDialog(QWidget* parent) { KoDialog dlg(parent); dlg.setCaption(i18n("Choose meta data merge strategy")); dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); dlg.setDefaultButton(KoDialog::Ok); KisMetaDataMergeStrategyChooserWidget* wdg = new KisMetaDataMergeStrategyChooserWidget(&dlg); wdg->setMinimumSize(wdg->sizeHint()); dlg.setMainWidget(wdg); if (dlg.exec() == QDialog::Accepted) { return wdg->currentStrategy(); } return 0; } diff --git a/libs/ui/widgets/kis_selection_options.cc b/libs/ui/widgets/kis_selection_options.cc index 6f86500d28..beda02c1f2 100644 --- a/libs/ui/widgets/kis_selection_options.cc +++ b/libs/ui/widgets/kis_selection_options.cc @@ -1,125 +1,169 @@ /* * Copyright (c) 2005 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_selection_options.h" #include #include #include #include #include #include #include #include "kis_types.h" #include "kis_layer.h" #include "kis_image.h" #include "kis_selection.h" #include "kis_paint_device.h" #include "canvas/kis_canvas2.h" #include "KisViewManager.h" KisSelectionOptions::KisSelectionOptions(KisCanvas2 * /*canvas*/) { m_page = new WdgSelectionOptions(this); Q_CHECK_PTR(m_page); QVBoxLayout * l = new QVBoxLayout(this); l->addWidget(m_page); l->addSpacerItem(new QSpacerItem(0,0, QSizePolicy::Preferred, QSizePolicy::Expanding)); l->setContentsMargins(0,0,0,0); m_mode = new QButtonGroup(this); m_mode->addButton(m_page->pixel, PIXEL_SELECTION); m_mode->addButton(m_page->shape, SHAPE_PROTECTION); m_action = new QButtonGroup(this); m_action->addButton(m_page->add, SELECTION_ADD); m_action->addButton(m_page->subtract, SELECTION_SUBTRACT); m_action->addButton(m_page->replace, SELECTION_REPLACE); m_action->addButton(m_page->intersect, SELECTION_INTERSECT); m_page->pixel->setGroupPosition(KoGroupButton::GroupLeft); m_page->shape->setGroupPosition(KoGroupButton::GroupRight); m_page->pixel->setIcon(KisIconUtils::loadIcon("select_pixel")); m_page->shape->setIcon(KisIconUtils::loadIcon("select_shape")); m_page->add->setGroupPosition(KoGroupButton::GroupCenter); m_page->subtract->setGroupPosition(KoGroupButton::GroupRight); m_page->replace->setGroupPosition(KoGroupButton::GroupLeft); m_page->intersect->setGroupPosition(KoGroupButton::GroupCenter); m_page->add->setIcon(KisIconUtils::loadIcon("selection_add")); m_page->subtract->setIcon(KisIconUtils::loadIcon("selection_subtract")); m_page->replace->setIcon(KisIconUtils::loadIcon("selection_replace")); m_page->intersect->setIcon(KisIconUtils::loadIcon("selection_intersect")); connect(m_mode, SIGNAL(buttonClicked(int)), this, SIGNAL(modeChanged(int))); connect(m_action, SIGNAL(buttonClicked(int)), this, SIGNAL(actionChanged(int))); connect(m_mode, SIGNAL(buttonClicked(int)), this, SLOT(hideActionsForSelectionMode(int))); - + connect(m_page->chkAntiAliasing, SIGNAL(toggled(bool)), this, SIGNAL(antiAliasSelectionChanged(bool))); } KisSelectionOptions::~KisSelectionOptions() { } int KisSelectionOptions::action() { return m_action->checkedId(); } void KisSelectionOptions::setAction(int action) { QAbstractButton* button = m_action->button(action); KIS_SAFE_ASSERT_RECOVER_RETURN(button); button->setChecked(true); } void KisSelectionOptions::setMode(int mode) { QAbstractButton* button = m_mode->button(mode); KIS_SAFE_ASSERT_RECOVER_RETURN(button); button->setChecked(true); hideActionsForSelectionMode(mode); } +void KisSelectionOptions::setAntiAliasSelection(bool value) +{ + m_page->chkAntiAliasing->setChecked(value); +} + +void KisSelectionOptions::updateActionButtonToolTip(int action, const QKeySequence &shortcut) +{ + const QString shortcutString = shortcut.toString(QKeySequence::NativeText); + QString toolTipText; + switch ((SelectionAction)action) { + case SELECTION_DEFAULT: + case SELECTION_REPLACE: + toolTipText = shortcutString.isEmpty() ? + i18nc("@info:tooltip", "Replace") : + i18nc("@info:tooltip", "Replace (%1)", shortcutString); + + m_action->button(SELECTION_REPLACE)->setToolTip(toolTipText); + break; + case SELECTION_ADD: + toolTipText = shortcutString.isEmpty() ? + i18nc("@info:tooltip", "Add") : + i18nc("@info:tooltip", "Add (%1)", shortcutString); + + m_action->button(SELECTION_ADD)->setToolTip(toolTipText); + break; + case SELECTION_SUBTRACT: + toolTipText = shortcutString.isEmpty() ? + i18nc("@info:tooltip", "Subtract") : + i18nc("@info:tooltip", "Subtract (%1)", shortcutString); + + m_action->button(SELECTION_SUBTRACT)->setToolTip(toolTipText); + + break; + case SELECTION_INTERSECT: + toolTipText = shortcutString.isEmpty() ? + i18nc("@info:tooltip", "Intersect") : + i18nc("@info:tooltip", "Intersect (%1)", shortcutString); + + m_action->button(SELECTION_INTERSECT)->setToolTip(toolTipText); + + break; + } +} + //hide action buttons and antialiasing, if shape selection is active (actions currently don't work on shape selection) void KisSelectionOptions::hideActionsForSelectionMode(int mode) { const bool isPixelSelection = (mode == (int)PIXEL_SELECTION); m_page->chkAntiAliasing->setVisible(isPixelSelection); } bool KisSelectionOptions::antiAliasSelection() { return m_page->chkAntiAliasing->isChecked(); } void KisSelectionOptions::disableAntiAliasSelectionOption() { m_page->chkAntiAliasing->hide(); disconnect(m_page->pixel, SIGNAL(clicked()), m_page->chkAntiAliasing, SLOT(show())); } void KisSelectionOptions::disableSelectionModeOption() { m_page->lblMode->hide(); m_page->pixel->hide(); m_page->shape->hide(); } diff --git a/libs/ui/widgets/kis_selection_options.h b/libs/ui/widgets/kis_selection_options.h index db8d780088..32d20c2889 100644 --- a/libs/ui/widgets/kis_selection_options.h +++ b/libs/ui/widgets/kis_selection_options.h @@ -1,74 +1,79 @@ /* * Copyright (c) 2005 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_SELECTION_OPTIONS_H__ #define __KIS_SELECTION_OPTIONS_H__ #include #include "kritaui_export.h" #include "ui_wdgselectionoptions.h" class KisCanvas2; class QButtonGroup; +class QKeySequence; class WdgSelectionOptions : public QWidget, public Ui::WdgSelectionOptions { Q_OBJECT public: WdgSelectionOptions(QWidget *parent) : QWidget(parent) { setupUi(this); } }; /** */ class KRITAUI_EXPORT KisSelectionOptions : public QWidget { Q_OBJECT public: KisSelectionOptions(KisCanvas2 * subject); ~KisSelectionOptions() override; int action(); bool antiAliasSelection(); void disableAntiAliasSelectionOption(); void disableSelectionModeOption(); void setAction(int); void setMode(int); + void setAntiAliasSelection(bool value); + + void updateActionButtonToolTip(int action, const QKeySequence &shortcut); Q_SIGNALS: void actionChanged(int); void modeChanged(int); + void antiAliasSelectionChanged(bool); private Q_SLOTS: void hideActionsForSelectionMode(int mode); private: WdgSelectionOptions * m_page; QButtonGroup* m_mode; QButtonGroup* m_action; }; #endif diff --git a/plugins/dockers/animation/timeline_frames_item_delegate.cpp b/plugins/dockers/animation/timeline_frames_item_delegate.cpp index 13a9a0ffd8..c56cf423eb 100644 --- a/plugins/dockers/animation/timeline_frames_item_delegate.cpp +++ b/plugins/dockers/animation/timeline_frames_item_delegate.cpp @@ -1,265 +1,265 @@ /* * 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_item_delegate.h" #include #include #include #include "krita_utils.h" #include "timeline_frames_model.h" #include "timeline_color_scheme.h" #include "kis_node_view_color_scheme.h" TimelineFramesItemDelegate::TimelineFramesItemDelegate(QObject *parent) : QItemDelegate(parent) { KisNodeViewColorScheme scm; labelColors = scm.allColorLabels(); } TimelineFramesItemDelegate::~TimelineFramesItemDelegate() { } void TimelineFramesItemDelegate::paintActiveFrameSelector(QPainter *painter, const QRect &rc, bool isCurrentFrame) { QColor lineColor = TimelineColorScheme::instance()->selectorColor(); const int lineWidth = rc.width() > 20 ? 4 : 2; const int x0 = rc.x(); const int y0 = rc.y(); const int x1 = rc.right(); const int y1 = rc.bottom(); QVector linesDark; linesDark << QLine(x0 + lineWidth / 2, y0, x0 + lineWidth / 2, y1); linesDark << QLine(x1 - lineWidth / 2 + 1, y0, x1 - lineWidth / 2 + 1, y1); QPen oldPen = painter->pen(); painter->setPen(QPen(lineColor, lineWidth)); painter->drawLines(linesDark); painter->setPen(oldPen); if (isCurrentFrame) { QPen oldPen = painter->pen(); QBrush oldBrush(painter->brush()); painter->setPen(QPen(lineColor, 0)); painter->setBrush(lineColor); painter->drawEllipse(rc.center(), 2,2); painter->setBrush(oldBrush); painter->setPen(oldPen); } } void TimelineFramesItemDelegate::paintSpecialKeyframeIndicator(QPainter *painter, const QModelIndex &index, const QRect &rc) const { bool doesFrameExist = index.data(TimelineFramesModel::FrameExistsRole).toBool(); /// does normal keyframe exist bool isEditable = index.data(TimelineFramesModel::FrameEditableRole).toBool(); bool hasContent = index.data(TimelineFramesModel::FrameHasContent).toBool(); /// find out if frame is empty QColor color = qApp->palette().color(QPalette::Highlight); QColor baseColor = qApp->palette().color(QPalette::Base); QColor noLabelSetColor = qApp->palette().color(QPalette::Highlight); // if no color label is used // use color label if it exists. coloring follows very similar logic to the drawBackground() function except this is a bit simpler QVariant colorLabel = index.data(TimelineFramesModel::FrameColorLabelIndexRole); if (colorLabel.isValid()) { color = labelColors.at(colorLabel.toInt()); } else { color = noLabelSetColor; } if (!isEditable) { color = KritaUtils::blendColors(baseColor, color, 0.5); } if (doesFrameExist && hasContent) { color = baseColor; // special keyframe will be over filled in frame, so switch color so it is shown } QPen oldPen = painter->pen(); QBrush oldBrush(painter->brush()); painter->setPen(QPen(color, 0)); painter->setBrush(color); QPointF center = rc.center(); QPointF points[4] = { QPointF(center.x() + 4, center.y() ), QPointF(center.x() , center.y() - 4), QPointF(center.x() - 4, center.y() ), QPointF(center.x() , center.y() + 4) }; painter->drawConvexPolygon(points, 4); painter->setBrush(oldBrush); painter->setPen(oldPen); } void TimelineFramesItemDelegate::drawBackground(QPainter *painter, const QModelIndex &index, const QRect &rc) const { /// is the current layer actively selected (this is not the same as visibility) bool hasActiveLayerRole = index.data(TimelineFramesModel::ActiveLayerRole).toBool(); bool doesFrameExist = index.data(TimelineFramesModel::FrameExistsRole).toBool(); /// does keyframe exist bool isEditable = index.data(TimelineFramesModel::FrameEditableRole).toBool(); /// is layer editable bool hasContent = index.data(TimelineFramesModel::FrameHasContent).toBool(); /// find out if frame is empty QColor color; // will get re-used for determining color QColor noLabelSetColor = qApp->palette().color(QPalette::Highlight); // if no color label is used QColor highlightColor = qApp->palette().color(QPalette::Highlight); QColor baseColor = qApp->palette().color(QPalette::Base); // pass for filling in the active row with slightly color difference if (hasActiveLayerRole) { color = KritaUtils::blendColors(baseColor, highlightColor, 0.8); painter->fillRect(rc, color); } // assign background color for frame depending on if the frame has a color label or not QVariant colorLabel = index.data(TimelineFramesModel::FrameColorLabelIndexRole); if (colorLabel.isValid()) { color = labelColors.at(colorLabel.toInt()); } else { color = noLabelSetColor; } // if layer is hidden, make the entire color more subtle. // this should be in effect for both fill color and empty outline color if (!isEditable) { color = KritaUtils::blendColors(baseColor, color, 0.7); } // how do we fill in a frame that has content // a keyframe will be totally filled in. A hold frame will have a line running through it if (hasContent && doesFrameExist) { painter->fillRect(rc, color); } // pass of outline for empty keyframes if (doesFrameExist && !hasContent) { QPen oldPen = painter->pen(); QBrush oldBrush(painter->brush()); painter->setPen(QPen(color, 2)); painter->setBrush(Qt::NoBrush); painter->drawRect(rc); painter->setBrush(oldBrush); painter->setPen(oldPen); } // pass of hold frame line if (!doesFrameExist && hasContent) { // pretty much the same check as "isValid" above, but that isn't working with hold frames if (colorLabel.toInt() == 0) { color = noLabelSetColor; if (!isEditable) { color = KritaUtils::blendColors(baseColor, color, 0.7); } } QPoint lineStart(rc.x(), (rc.y() + rc.height()/2)); QPoint lineEnd(rc.x() + rc.width(), (rc.y() + rc.height()/2)); QPen holdFramePen(color); holdFramePen.setWidth(1); painter->setPen(holdFramePen); painter->drawLine(QLine(lineStart, lineEnd)); } } void TimelineFramesItemDelegate::drawFocus(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) const { - // copied form Qt 4.8! + // copied from Qt 4.8! if ((option.state & QStyle::State_HasFocus) == 0 || !rect.isValid()) return; QStyleOptionFocusRect o; o.QStyleOption::operator=(option); o.rect = rect; o.state |= QStyle::State_KeyboardFocusChange; o.state |= QStyle::State_Item; QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Window); const QWidget *widget = qobject_cast(parent()); QStyle *style = widget ? widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, widget); } void TimelineFramesItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { // draws background as well as fills normal keyframes drawBackground(painter, index, option.rect); // creates a semi transparent orange rectangle in the frame that is actively selected on the active row if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) { QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) cg = QPalette::Inactive; QBrush brush = TimelineColorScheme::instance()->selectionColor(); int oldOpacity = painter->opacity(); painter->setOpacity(0.5); painter->fillRect(option.rect, brush); painter->setOpacity(oldOpacity); } // not sure what this is drawing drawFocus(painter, option, option.rect); // opacity keyframe, but NOT normal keyframes bool specialKeys = index.data(TimelineFramesModel::SpecialKeyframeExists).toBool(); if (specialKeys) { paintSpecialKeyframeIndicator(painter, index, option.rect); } // creates a border and dot on the selected frame on the active row bool active = index.data(TimelineFramesModel::ActiveFrameRole).toBool(); bool layerIsCurrent = index.data(TimelineFramesModel::ActiveLayerRole).toBool(); if (active) { paintActiveFrameSelector(painter, option.rect, layerIsCurrent); } } diff --git a/plugins/dockers/gamutmask/gamutmask_dock.cpp b/plugins/dockers/gamutmask/gamutmask_dock.cpp index b5aae4d1cf..5343eefe7a 100644 --- a/plugins/dockers/gamutmask/gamutmask_dock.cpp +++ b/plugins/dockers/gamutmask/gamutmask_dock.cpp @@ -1,603 +1,629 @@ /* * Copyright (c) 2018 Anna Medonosova * * 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 #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 "gamutmask_dock.h" #include #include #include #include #include #include "ui_wdgGamutMaskChooser.h" class KisMainWindow; struct GamutMaskChooserUI: public QWidget, public Ui_wdgGamutMaskChooser { GamutMaskChooserUI() { setupUi(this); } }; GamutMaskDock::GamutMaskDock() : QDockWidget(i18n("Gamut Masks")) , m_resourceProvider(0) , m_selfClosingTemplate(false) , m_externalTemplateClose(false) , m_creatingNewMask(false) , m_templatePrevSaved(false) , m_selfSelectingMask(false) , m_selectedMask(nullptr) , m_maskDocument(nullptr) , m_view(nullptr) { m_dockerUI = new GamutMaskChooserUI(); m_dockerUI->bnMaskEditor->setIcon(KisIconUtils::loadIcon("dirty-preset")); m_dockerUI->bnMaskDelete->setIcon(KisIconUtils::loadIcon("deletelayer")); m_dockerUI->bnMaskNew->setIcon(KisIconUtils::loadIcon("list-add")); m_dockerUI->bnMaskDuplicate->setIcon(KisIconUtils::loadIcon("duplicatelayer")); m_dockerUI->maskPropertiesBox->setVisible(false); m_dockerUI->bnSaveMask->setIcon(KisIconUtils::loadIcon("document-save")); m_dockerUI->bnCancelMaskEdit->setIcon(KisIconUtils::loadIcon("dialog-cancel")); m_dockerUI->bnPreviewMask->setIcon(KisIconUtils::loadIcon("visible")); QRegularExpression maskTitleRegex("^[-_\\(\\)\\sA-Za-z0-9]+$"); QRegularExpressionValidator* m_maskTitleValidator = new QRegularExpressionValidator(maskTitleRegex, this); m_dockerUI->maskTitleEdit->setValidator(m_maskTitleValidator); KoResourceServer* rServer = KoResourceServerProvider::instance()->gamutMaskServer(); rServer->addObserver(this); // gamut mask connections connect(m_dockerUI->bnSaveMask , SIGNAL(clicked()) , SLOT(slotGamutMaskSave())); connect(m_dockerUI->bnCancelMaskEdit , SIGNAL(clicked()) , SLOT(slotGamutMaskCancelEdit())); connect(m_dockerUI->bnPreviewMask , SIGNAL(clicked()) , SLOT(slotGamutMaskPreview())); connect(m_dockerUI->bnMaskEditor , SIGNAL(clicked()) , SLOT(slotGamutMaskEdit())); connect(m_dockerUI->maskChooser, SIGNAL(sigGamutMaskSelected(KoGamutMask*)), SLOT(slotGamutMaskSelected(KoGamutMask*))); connect(m_dockerUI->bnMaskNew , SIGNAL(clicked()) , SLOT(slotGamutMaskCreateNew())); connect(m_dockerUI->bnMaskDelete , SIGNAL(clicked()) , SLOT(slotGamutMaskDelete())); connect(m_dockerUI->bnMaskDuplicate , SIGNAL(clicked()) , SLOT(slotGamutMaskDuplicate())); setWidget(m_dockerUI); } GamutMaskDock::~GamutMaskDock() { KoResourceServer* rServer = KoResourceServerProvider::instance()->gamutMaskServer(); rServer->removeObserver(this); } void GamutMaskDock::setViewManager(KisViewManager* kisview) { m_resourceProvider = kisview->resourceProvider(); selectMask(m_resourceProvider->currentGamutMask()); connect(this, SIGNAL(sigGamutMaskSet(KoGamutMask*)), m_resourceProvider, SLOT(slotGamutMaskActivated(KoGamutMask*))); connect(this, SIGNAL(sigGamutMaskChanged(KoGamutMask*)), m_resourceProvider, SLOT(slotGamutMaskActivated(KoGamutMask*))); connect(this, SIGNAL(sigGamutMaskUnset()), m_resourceProvider, SLOT(slotGamutMaskUnset())); connect(this, SIGNAL(sigGamutMaskPreviewUpdate()), m_resourceProvider, SLOT(slotGamutMaskPreviewUpdate())); connect(KisPart::instance(), SIGNAL(sigDocumentRemoved(QString)), this, SLOT(slotDocumentRemoved(QString))); } void GamutMaskDock::slotGamutMaskEdit() { if (!m_selectedMask) { return; } openMaskEditor(); } -void GamutMaskDock::openMaskEditor() +bool GamutMaskDock::openMaskEditor() { if (!m_selectedMask) { - return; + return false; + } + + // find the template resource first, so we can abort the action early on + QString maskTemplateFile = KoResourcePaths::findResource("ko_gamutmasks", "GamutMaskTemplate.kra"); + if (maskTemplateFile.isEmpty() || maskTemplateFile.isNull() || !QFile::exists(maskTemplateFile)) { + dbgPlugins << "GamutMaskDock::openMaskEditor(): maskTemplateFile (" << maskTemplateFile << ") was not found on the system"; + getUserFeedback(i18n("Could not open gamut mask for editing."), + i18n("The editor template was not found."), + QMessageBox::Ok, QMessageBox::Ok, QMessageBox::Critical); + return false; } m_dockerUI->maskPropertiesBox->setVisible(true); m_dockerUI->maskPropertiesBox->setEnabled(true); m_dockerUI->editControlsBox->setEnabled(false); m_dockerUI->editControlsBox->setVisible(false); m_dockerUI->maskTitleEdit->setText(m_selectedMask->title()); m_dockerUI->maskDescriptionEdit->setPlainText(m_selectedMask->description()); - // open gamut mask template in the application - QString maskTemplateFile = KoResourcePaths::findResource("data", "gamutmasks/GamutMaskTemplate.kra"); - m_maskDocument = KisPart::instance()->createDocument(); KisPart::instance()->addDocument(m_maskDocument); m_maskDocument->openUrl(QUrl::fromLocalFile(maskTemplateFile), KisDocument::DontAddToRecent); // template document needs a proper autogenerated filename, // to avoid collision with other documents, // otherwise bugs happen when slotDocumentRemoved is called // (e.g. user closes another view, the template stays open, but the edit operation is canceled) m_maskDocument->setInfiniteAutoSaveInterval(); QString maskPath = QString("%1%2%3_%4.kra") .arg(QDir::tempPath()) .arg(QDir::separator()) .arg("GamutMaskTemplate") .arg(std::time(nullptr)); m_maskDocument->setUrl(QUrl::fromLocalFile(maskPath)); m_maskDocument->setLocalFilePath(maskPath); KisShapeLayerSP shapeLayer = getShapeLayer(); // pass only copies of shapes to the layer, // so the originals don't disappear from the mask later for (KoShape *shape: m_selectedMask->koShapes()) { KoShape* newShape = shape->cloneShape(); newShape->setStroke(KoShapeStrokeModelSP()); newShape->setBackground(QSharedPointer(new KoColorBackground(QColor(255,255,255)))); shapeLayer->addShape(newShape); } m_maskDocument->setPreActivatedNode(shapeLayer); // set document as active KisMainWindow* mainWindow = KisPart::instance()->currentMainwindow(); KIS_ASSERT(mainWindow); m_view = mainWindow->addViewAndNotifyLoadingCompleted(m_maskDocument); KIS_ASSERT(m_view); for(KisView *view: KisPart::instance()->views()) { if (view->document() == m_maskDocument) { view->activateWindow(); break; } } connect(m_view->viewManager(), SIGNAL(viewChanged()), this, SLOT(slotViewChanged())); connect(m_maskDocument, SIGNAL(completed()), this, SLOT(slotDocumentSaved())); + + return true; } void GamutMaskDock::cancelMaskEdit() { if (m_creatingNewMask) { deleteMask(); } if (m_selectedMask) { m_selectedMask->clearPreview(); if (m_resourceProvider->currentGamutMask() == m_selectedMask) { emit sigGamutMaskChanged(m_selectedMask); } } closeMaskDocument(); } void GamutMaskDock::selectMask(KoGamutMask *mask, bool notifyItemChooser) { if (!mask) { return; } m_selectedMask = mask; if (notifyItemChooser) { m_selfSelectingMask = true; m_dockerUI->maskChooser->setCurrentResource(m_selectedMask); m_selfSelectingMask = false; } emit sigGamutMaskSet(m_selectedMask); } bool GamutMaskDock::saveSelectedMaskResource() { if (!m_selectedMask || !m_maskDocument) { return false; } bool maskSaved = false; if (m_selectedMask) { QList shapes = getShapesFromLayer(); if (shapes.count() > 0) { m_selectedMask->setMaskShapes(shapes); m_selectedMask->setImage( m_maskDocument->image()->convertToQImage(m_maskDocument->image()->bounds() , m_maskDocument->image()->profile() ) ); m_selectedMask->setDescription(m_dockerUI->maskDescriptionEdit->toPlainText()); m_selectedMask->clearPreview(); m_selectedMask->save(); maskSaved = true; } else { - getUserFeedback(i18n("

Saving of gamut mask '%1' was aborted.

" - "

The mask template is invalid.

" + getUserFeedback(i18n("Saving of gamut mask '%1' was aborted.", m_selectedMask->title()), + i18n("

The mask template is invalid.

" "

Please check that:" "

    " "
  • your template contains a vector layer named 'maskShapesLayer'
  • " "
  • there are one or more vector shapes on the 'maskShapesLayer'
  • " "

" - , m_selectedMask->title()), + ), QMessageBox::Ok, QMessageBox::Ok); } } return maskSaved; } void GamutMaskDock::deleteMask() { KoResourceServer* rServer = KoResourceServerProvider::instance()->gamutMaskServer(); rServer->removeResourceAndBlacklist(m_selectedMask); m_selectedMask = nullptr; } -int GamutMaskDock::getUserFeedback(QString message, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) +int GamutMaskDock::getUserFeedback(QString text, QString informativeText, + QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton, + QMessageBox::Icon severity) { - int res = QMessageBox::warning(this, - i18nc("@title:window", "Krita"), - message, - buttons, defaultButton); + QMessageBox msgBox; + msgBox.setWindowTitle(i18nc("@title:window", "Krita")); + msgBox.setText(QString("

%1

").arg(text)); + msgBox.setInformativeText(informativeText); + msgBox.setStandardButtons(buttons); + msgBox.setDefaultButton(defaultButton); + msgBox.setIcon(severity); + int res = msgBox.exec(); return res; } int GamutMaskDock::saveOrCancel(QMessageBox::StandardButton defaultAction) { int response = 0; if (m_maskDocument->isModified()) { - response = getUserFeedback(i18n("

Gamut mask '%1' has been modified.

Do you want to save it?

" - , m_selectedMask->title()), - QMessageBox::Cancel | QMessageBox::Close | QMessageBox::Save, defaultAction); + response = getUserFeedback(i18n("Gamut mask '%1' has been modified.", m_selectedMask->title()), + i18n("Do you want to save it?"), + QMessageBox::Cancel | QMessageBox::Close | QMessageBox::Save, defaultAction); } else if (m_templatePrevSaved && defaultAction != QMessageBox::Close) { response = QMessageBox::Save; } else if (!m_templatePrevSaved) { response = QMessageBox::Close; } else { response = defaultAction; } switch (response) { case QMessageBox::Save : { slotGamutMaskSave(); break; } case QMessageBox::Close : { cancelMaskEdit(); break; } } return response; } KoGamutMask *GamutMaskDock::createMaskResource(KoGamutMask* sourceMask, QString newTitle) { m_creatingNewMask = true; KoGamutMask* newMask = nullptr; if (sourceMask) { newMask = new KoGamutMask(sourceMask); newMask->setImage(sourceMask->image()); } else { newMask = new KoGamutMask(); - QString defaultPreviewPath = KoResourcePaths::findResource("data", "gamutmasks/empty_mask_preview.png"); + + QString defaultPreviewPath = KoResourcePaths::findResource("ko_gamutmasks", "empty_mask_preview.png"); + KIS_SAFE_ASSERT_RECOVER_NOOP(!(defaultPreviewPath.isEmpty() || defaultPreviewPath.isNull() || !QFile::exists(defaultPreviewPath))); + newMask->setImage(QImage(defaultPreviewPath, "PNG")); } QPair maskFile = resolveMaskTitle(newTitle); QString maskTitle = maskFile.first; QFileInfo fileInfo = maskFile.second; newMask->setTitle(maskTitle); newMask->setFilename(fileInfo.filePath()); newMask->setValid(true); KoResourceServer* rServer = KoResourceServerProvider::instance()->gamutMaskServer(); rServer->removeFromBlacklist(newMask); rServer->addResource(newMask, false); return newMask; } QPair GamutMaskDock::resolveMaskTitle(QString suggestedTitle) { KoResourceServer* rServer = KoResourceServerProvider::instance()->gamutMaskServer(); QString saveLocation = rServer->saveLocation(); QString processedTitle = suggestedTitle.trimmed(); QString resourceName = processedTitle; while (rServer->resourceByName(resourceName)) { resourceName = resourceName + QString(" (Copy)"); } QString maskTitle = resourceName; QString maskFile = maskTitle + ".kgm"; QString path = saveLocation + maskFile.replace(QRegularExpression("\\s+"), "_"); QFileInfo fileInfo(path); return QPair(maskTitle, fileInfo); } void GamutMaskDock::closeMaskDocument() { if (!m_externalTemplateClose) { if (m_maskDocument) { // set the document to not modified to bypass confirmation dialog // the close is already confirmed m_maskDocument->setModified(false); m_maskDocument->closeUrl(); m_view->closeView(); m_view->deleteLater(); // set a flag that we are doing it ourselves, so the docker does not react to // removing signal from KisPart m_selfClosingTemplate = true; KisPart::instance()->removeView(m_view); KisPart::instance()->removeDocument(m_maskDocument); m_selfClosingTemplate = false; } } m_dockerUI->maskPropertiesBox->setVisible(false); m_dockerUI->editControlsBox->setVisible(true); m_dockerUI->editControlsBox->setEnabled(true); disconnect(m_view->viewManager(), SIGNAL(viewChanged()), this, SLOT(slotViewChanged())); disconnect(m_maskDocument, SIGNAL(completed()), this, SLOT(slotDocumentSaved())); // the template file is meant as temporary, if the user saved it, delete now if (QFile::exists(m_maskDocument->localFilePath())) { QFile::remove(m_maskDocument->localFilePath()); } m_maskDocument = nullptr; m_view = nullptr; m_creatingNewMask = false; m_templatePrevSaved = false; } QList GamutMaskDock::getShapesFromLayer() { KisShapeLayerSP shapeLayer = getShapeLayer(); // create a deep copy of the shapes to save in the mask, // otherwise they vanish when the template closes QList newShapes; if (shapeLayer) { for (KoShape* sh: shapeLayer->shapes()) { KoShape* newShape = sh->cloneShape(); KoShapeStrokeSP border(new KoShapeStroke(0.5f, Qt::white)); newShape->setStroke(border); newShape->setBackground(QSharedPointer(new KoColorBackground(QColor(255,255,255,0)))); newShapes.append(newShape); } } return newShapes; } KisShapeLayerSP GamutMaskDock::getShapeLayer() { KisNodeSP node = m_maskDocument->image()->rootLayer()->findChildByName("maskShapesLayer"); return KisShapeLayerSP(dynamic_cast(node.data())); } void GamutMaskDock::slotGamutMaskSave() { if (!m_selectedMask || !m_maskDocument) { return; } QString newTitle = m_dockerUI->maskTitleEdit->text(); if (m_selectedMask->title() != newTitle) { // title has changed, rename KoGamutMask* newMask = createMaskResource(m_selectedMask, newTitle); // delete old mask and select new deleteMask(); selectMask(newMask); } bool maskSaved = saveSelectedMaskResource(); if (maskSaved) { emit sigGamutMaskSet(m_selectedMask); closeMaskDocument(); } } void GamutMaskDock::slotGamutMaskCancelEdit() { if (!m_selectedMask) { return; } saveOrCancel(QMessageBox::Close); } void GamutMaskDock::slotGamutMaskPreview() { if (!m_selectedMask) { return; } m_selectedMask->setPreviewMaskShapes(getShapesFromLayer()); emit sigGamutMaskPreviewUpdate(); } void GamutMaskDock::slotGamutMaskSelected(KoGamutMask *mask) { if (!m_selfSelectingMask) { if (m_maskDocument) { int res = saveOrCancel(); if (res == QMessageBox::Cancel) { return; } } selectMask(mask, false); } } void GamutMaskDock::setCanvas(KoCanvasBase *canvas) { setEnabled(canvas != 0); } void GamutMaskDock::unsetCanvas() { setEnabled(false); } void GamutMaskDock::unsetResourceServer() { KoResourceServer* rServer = KoResourceServerProvider::instance()->gamutMaskServer(); rServer->removeObserver(this); } void GamutMaskDock::removingResource(KoGamutMask *resource) { // if deleting previously set mask, notify selectors to unset their mask if (resource == m_resourceProvider->currentGamutMask()) { emit sigGamutMaskUnset(); m_selectedMask = nullptr; } } void GamutMaskDock::resourceChanged(KoGamutMask *resource) { // if currently set mask has been changed, notify selectors if (resource == m_resourceProvider->currentGamutMask()) { selectMask(resource); } } void GamutMaskDock::slotGamutMaskCreateNew() { KoGamutMask* newMask = createMaskResource(nullptr, "new mask"); selectMask(newMask); - openMaskEditor(); + + bool editorOpened = openMaskEditor(); + if (!editorOpened) { + deleteMask(); + } } void GamutMaskDock::slotGamutMaskDuplicate() { if (!m_selectedMask) { return; } KoGamutMask* newMask = createMaskResource(m_selectedMask, m_selectedMask->title()); selectMask(newMask); - openMaskEditor(); + + bool editorOpened = openMaskEditor(); + if (!editorOpened) { + deleteMask(); + } } void GamutMaskDock::slotGamutMaskDelete() { if (!m_selectedMask) { return; } int res = getUserFeedback(i18n("Are you sure you want to delete mask '%1'?" , m_selectedMask->title())); if (res == QMessageBox::Yes) { deleteMask(); } } void GamutMaskDock::slotDocumentRemoved(QString filename) { if (!m_maskDocument) { return; } m_externalTemplateClose = true; // we do not want to run this if it is we who close the file if (!m_selfClosingTemplate) { // KisPart called, that a document will be removed // if it's ours, cancel the mask edit operation if (m_maskDocument->url().toLocalFile() == filename) { m_maskDocument->waitForSavingToComplete(); saveOrCancel(); } } m_externalTemplateClose = false; } void GamutMaskDock::slotViewChanged() { if (!m_maskDocument || !m_view) { return; } if (m_view->viewManager()->document() == m_maskDocument) { m_dockerUI->maskPropertiesBox->setEnabled(true); } else { m_dockerUI->maskPropertiesBox->setEnabled(false); } } void GamutMaskDock::slotDocumentSaved() { m_templatePrevSaved = true; } diff --git a/plugins/dockers/gamutmask/gamutmask_dock.h b/plugins/dockers/gamutmask/gamutmask_dock.h index 069c0f998a..43e14d0af7 100644 --- a/plugins/dockers/gamutmask/gamutmask_dock.h +++ b/plugins/dockers/gamutmask/gamutmask_dock.h @@ -1,124 +1,125 @@ /* * Copyright (c) 2018 Anna Medonosova * * 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 H_GAMUT_MASK_DOCK_H #define H_GAMUT_MASK_DOCK_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KisCanvasResourceProvider; class QButtonGroup; class QMenu; struct GamutMaskChooserUI; class GamutMaskDock: public QDockWidget, public KisMainwindowObserver, public KoResourceServerObserver { Q_OBJECT public: GamutMaskDock(); ~GamutMaskDock() override; QString observerName() override { return "GamutMaskDock"; } void setViewManager(KisViewManager* kisview) override; void setCanvas(KoCanvasBase *canvas) override; void unsetCanvas() override; public: // KoResourceServerObserver void unsetResourceServer() override; void resourceAdded(KoGamutMask* /*resource*/) override {}; void removingResource(KoGamutMask* resource) override; void resourceChanged(KoGamutMask* resource) override; void syncTaggedResourceView() override {} void syncTagAddition(const QString&) override {} void syncTagRemoval(const QString&) override {} Q_SIGNALS: void sigGamutMaskSet(KoGamutMask* mask); void sigGamutMaskChanged(KoGamutMask* mask); void sigGamutMaskUnset(); void sigGamutMaskPreviewUpdate(); private Q_SLOTS: void slotGamutMaskEdit(); void slotGamutMaskSave(); void slotGamutMaskCancelEdit(); void slotGamutMaskSelected(KoGamutMask* mask); void slotGamutMaskPreview(); void slotGamutMaskCreateNew(); void slotGamutMaskDuplicate(); void slotGamutMaskDelete(); void slotDocumentRemoved(QString filename); void slotViewChanged(); void slotDocumentSaved(); private: void closeMaskDocument(); - void openMaskEditor(); + bool openMaskEditor(); void cancelMaskEdit(); void selectMask(KoGamutMask* mask, bool notifyItemChooser = true); bool saveSelectedMaskResource(); void deleteMask(); - int getUserFeedback(QString message - , QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No - , QMessageBox::StandardButton defaultButton = QMessageBox::Yes); + int getUserFeedback(QString text, QString informativeText = "", + QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No, + QMessageBox::StandardButton defaultButton = QMessageBox::Yes, + QMessageBox::Icon severity = QMessageBox::Warning); int saveOrCancel(QMessageBox::StandardButton defaultAction = QMessageBox::Save); KoGamutMask* createMaskResource(KoGamutMask* sourceMask, QString newTitle); QPair resolveMaskTitle(QString suggestedTitle); QList getShapesFromLayer(); KisShapeLayerSP getShapeLayer(); KisCanvasResourceProvider* m_resourceProvider; bool m_selfClosingTemplate; bool m_externalTemplateClose; bool m_creatingNewMask; bool m_templatePrevSaved; bool m_selfSelectingMask; GamutMaskChooserUI* m_dockerUI; KoResourceItemChooser* m_maskChooser; KoGamutMask* m_selectedMask; QRegExpValidator* m_maskTitleValidator; KisDocument* m_maskDocument; KisView* m_view; }; #endif // H_GAMUT_MASK_DOCK_H diff --git a/plugins/extensions/imagesize/imagesize.cc b/plugins/extensions/imagesize/imagesize.cc index 7adb79aaa0..236afc02f1 100644 --- a/plugins/extensions/imagesize/imagesize.cc +++ b/plugins/extensions/imagesize/imagesize.cc @@ -1,166 +1,198 @@ /* * imagesize.cc -- Part of Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * * 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 "imagesize.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dlg_imagesize.h" #include "dlg_canvassize.h" #include "dlg_layersize.h" #include "kis_filter_strategy.h" #include "kis_action.h" #include "kis_action_manager.h" K_PLUGIN_FACTORY_WITH_JSON(ImageSizeFactory, "kritaimagesize.json", registerPlugin();) ImageSize::ImageSize(QObject *parent, const QVariantList &) : KisActionPlugin(parent) { KisAction *action = createAction("imagesize"); connect(action, SIGNAL(triggered()), this, SLOT(slotImageSize())); action = createAction("canvassize"); connect(action, SIGNAL(triggered()), this, SLOT(slotCanvasSize())); action = createAction("layersize"); connect(action, SIGNAL(triggered()), this, SLOT(slotLayerSize())); + action = createAction("scaleAllLayers"); + connect(action, SIGNAL(triggered()), this, SLOT(slotScaleAllLayers())); + action = createAction("selectionscale"); connect(action, SIGNAL(triggered()), this, SLOT(slotSelectionScale())); } ImageSize::~ImageSize() { } void ImageSize::slotImageSize() { KisImageSP image = viewManager()->image().toStrongRef(); if (!image) return; + if(!viewManager()->blockUntilOperationsFinished(image)) return; + DlgImageSize * dlgImageSize = new DlgImageSize(viewManager()->mainWindow(), image->width(), image->height(), image->yRes()); Q_CHECK_PTR(dlgImageSize); dlgImageSize->setObjectName("ImageSize"); if (dlgImageSize->exec() == QDialog::Accepted) { qint32 w = dlgImageSize->width(); qint32 h = dlgImageSize->height(); double res = dlgImageSize->resolution(); viewManager()->imageManager()->scaleCurrentImage(QSize(w, h), res, res, dlgImageSize->filterType()); } delete dlgImageSize; } void ImageSize::slotCanvasSize() { KisImageWSP image = viewManager()->image(); - if (!image) return; + if(!viewManager()->blockUntilOperationsFinished(image)) return; + DlgCanvasSize * dlgCanvasSize = new DlgCanvasSize(viewManager()->mainWindow(), image->width(), image->height(), image->yRes()); Q_CHECK_PTR(dlgCanvasSize); if (dlgCanvasSize->exec() == QDialog::Accepted) { qint32 width = dlgCanvasSize->width(); qint32 height = dlgCanvasSize->height(); qint32 xOffset = dlgCanvasSize->xOffset(); qint32 yOffset = dlgCanvasSize->yOffset(); viewManager()->imageManager()->resizeCurrentImage(width, height, xOffset, yOffset); } delete dlgCanvasSize; } -void ImageSize::slotLayerSize() +void ImageSize::scaleLayerImpl(KisNodeSP rootNode) { KisImageWSP image = viewManager()->image(); - if (!image) return; - KisPaintDeviceSP dev = viewManager()->activeLayer()->projection(); - Q_ASSERT(dev); - QRect rc = dev->exactBounds(); + if(!viewManager()->blockUntilOperationsFinished(image)) return; - DlgLayerSize * dlgLayerSize = new DlgLayerSize(viewManager()->mainWindow(), "LayerSize", rc.width(), rc.height(), image->yRes()); + QRect bounds; + KisSelectionSP selection = viewManager()->selection(); + + if (selection) { + bounds = selection->selectedExactRect(); + } else { + KisPaintDeviceSP dev = rootNode->projection(); + KIS_SAFE_ASSERT_RECOVER_RETURN(dev); + bounds = dev->exactBounds(); + } + + DlgLayerSize * dlgLayerSize = new DlgLayerSize(viewManager()->mainWindow(), "LayerSize", bounds.width(), bounds.height(), image->yRes()); Q_CHECK_PTR(dlgLayerSize); dlgLayerSize->setCaption(i18n("Resize Layer")); if (dlgLayerSize->exec() == QDialog::Accepted) { qint32 w = dlgLayerSize->width(); qint32 h = dlgLayerSize->height(); - viewManager()->nodeManager()->scale((double)w / ((double)(rc.width())), - (double)h / ((double)(rc.height())), - dlgLayerSize->filterType()); + viewManager()->image()->scaleNode(rootNode, + QRectF(bounds).center(), + qreal(w) / bounds.width(), + qreal(h) / bounds.height(), + dlgLayerSize->filterType(), + selection); } delete dlgLayerSize; } +void ImageSize::slotLayerSize() +{ + scaleLayerImpl(viewManager()->activeNode()); +} + +void ImageSize::slotScaleAllLayers() +{ + KisImageWSP image = viewManager()->image(); + if (!image) return; + + scaleLayerImpl(image->root()); +} + void ImageSize::slotSelectionScale() { KisImageSP image = viewManager()->image(); - if (!image) { - return; - } + if (!image) return; + + if(!viewManager()->blockUntilOperationsFinished(image)) return; + KisLayerSP layer = viewManager()->activeLayer(); KIS_ASSERT_RECOVER_RETURN(image && layer); KisSelectionMaskSP selectionMask = layer->selectionMask(); if (!selectionMask) { selectionMask = image->rootLayer()->selectionMask(); } KIS_ASSERT_RECOVER_RETURN(selectionMask); QRect rc = selectionMask->selection()->selectedExactRect(); DlgLayerSize * dlgSize = new DlgLayerSize(viewManager()->mainWindow(), "SelectionScale", rc.width(), rc.height(), image->yRes()); dlgSize->setCaption(i18n("Scale Selection")); if (dlgSize->exec() == QDialog::Accepted) { qint32 w = dlgSize->width(); qint32 h = dlgSize->height(); image->scaleNode(selectionMask, + QRectF(rc).center(), qreal(w) / rc.width(), qreal(h) / rc.height(), - dlgSize->filterType()); + dlgSize->filterType(), 0); } delete dlgSize; } #include "imagesize.moc" diff --git a/plugins/extensions/imagesize/imagesize.h b/plugins/extensions/imagesize/imagesize.h index 892fc333ee..bcf1093031 100644 --- a/plugins/extensions/imagesize/imagesize.h +++ b/plugins/extensions/imagesize/imagesize.h @@ -1,42 +1,48 @@ /* * imagesize.h -- Part of Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * * 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 IMAGESIZE_H #define IMAGESIZE_H #include #include +#include "kis_types.h" class ImageSize : public KisActionPlugin { Q_OBJECT public: ImageSize(QObject *parent, const QVariantList &); ~ImageSize() override; +private: + void scaleLayerImpl(KisNodeSP rootNode); + private Q_SLOTS: void slotImageSize(); void slotCanvasSize(); void slotLayerSize(); void slotSelectionScale(); + + void slotScaleAllLayers(); }; #endif // IMAGESIZE_H diff --git a/plugins/extensions/metadataeditor/kis_entry_editor.cc b/plugins/extensions/metadataeditor/kis_entry_editor.cc index e5040d22a7..046b471eb0 100644 --- a/plugins/extensions/metadataeditor/kis_entry_editor.cc +++ b/plugins/extensions/metadataeditor/kis_entry_editor.cc @@ -1,100 +1,105 @@ /* * 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; 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 "kis_entry_editor.h" #include #include #include -#include -#include -#include +#include +#include +#include struct KisEntryEditor::Private { - QObject* object; + QWidget* object; QString propertyName; KisMetaData::Store* store; QString key; QString structField; int arrayIndex; + KisMetaData::Value value() { KisMetaData::Value value = store->getEntry(key).value(); + if (value.type() == KisMetaData::Value::Structure && !structField.isEmpty()) { QMap structure = value.asStructure(); return structure[ structField ]; - } else if (value.isArray() && arrayIndex > -1) { + } + else if (value.isArray() && arrayIndex > -1) { QList array = value.asArray(); if (arrayIndex < array.size()) { return array[arrayIndex]; } else { return KisMetaData::Value(); } } return value; } void setValue(const QVariant& variant) { KisMetaData::Value& value = store->getEntry(key).value(); if (value.type() == KisMetaData::Value::Structure && !structField.isEmpty()) { QMap structure = value.asStructure(); value = structure[ structField ]; value.setVariant(variant); value.setStructureVariant(structField, variant); } else if (value.isArray() && arrayIndex > -1) { value.setArrayVariant(arrayIndex, variant); } else { value.setVariant(variant); } } }; -KisEntryEditor::KisEntryEditor(QObject* obj, KisMetaData::Store* store, QString key, QString propertyName, QString structField, int arrayIndex) : d(new Private) +KisEntryEditor::KisEntryEditor(QWidget* obj, KisMetaData::Store* store, QString key, QString propertyName, QString structField, int arrayIndex) + : d(new Private) { Q_ASSERT(obj); Q_ASSERT(store); d->object = obj; d->propertyName = propertyName; d->store = store; d->key = key; d->structField = structField; d->arrayIndex = arrayIndex; valueChanged(); } KisEntryEditor::~KisEntryEditor() { delete d; } void KisEntryEditor::valueChanged() { if (d->store->containsEntry(d->key)) { bool blocked = d->object->blockSignals(true); - d->object->setProperty(d->propertyName.toLatin1(), d->value().asVariant()); + KisMetaData::Value val = d->value(); + d->object->setProperty(d->propertyName.toLatin1(), val.asVariant()); d->object->blockSignals(blocked); } } void KisEntryEditor::valueEdited() { QVariant val = d->object->property(d->propertyName.toLatin1()); dbgMetaData << "Value edited: " << d->propertyName << val; d->setValue(val); emit valueHasBeenEdited(); } diff --git a/plugins/extensions/metadataeditor/kis_entry_editor.h b/plugins/extensions/metadataeditor/kis_entry_editor.h index 53fd109632..f10d38ee1f 100644 --- a/plugins/extensions/metadataeditor/kis_entry_editor.h +++ b/plugins/extensions/metadataeditor/kis_entry_editor.h @@ -1,47 +1,47 @@ /* * 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; 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 _KIS_ENTRY_EDITOR_H_ #define _KIS_ENTRY_EDITOR_H_ -#include +#include class QString; namespace KisMetaData { class Store; } class KisEntryEditor : public QObject { Q_OBJECT struct Private; public: - KisEntryEditor(QObject* obj, KisMetaData::Store* store, QString key, QString propertyName, QString structField, int arrayIndex); + KisEntryEditor(QWidget *obj, KisMetaData::Store* store, QString key, QString propertyName, QString structField, int arrayIndex); ~KisEntryEditor() override; public Q_SLOTS: void valueEdited(); void valueChanged(); Q_SIGNALS: void valueHasBeenEdited(); private: Private* const d; }; #endif diff --git a/plugins/extensions/metadataeditor/kis_meta_data_editor.cc b/plugins/extensions/metadataeditor/kis_meta_data_editor.cc index 47b87c9bb9..e24865b7e0 100644 --- a/plugins/extensions/metadataeditor/kis_meta_data_editor.cc +++ b/plugins/extensions/metadataeditor/kis_meta_data_editor.cc @@ -1,156 +1,159 @@ /* * 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; 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 "kis_meta_data_editor.h" #include #include #include #include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include "kis_entry_editor.h" #include #include "kis_meta_data_model.h" #include struct KisMetaDataEditor::Private { KisMetaData::Store* originalStore; KisMetaData::Store* store; QMultiHash entryEditors; }; KisMetaDataEditor::KisMetaDataEditor(QWidget* parent, KisMetaData::Store* originalStore) : KPageDialog(parent), d(new Private) { d->originalStore = originalStore; d->store = new KisMetaData::Store(*originalStore); QStringList files = KoResourcePaths::findAllResources("data", "kritaplugins/metadataeditor/*.xmlgui"); QMap widgets; widgets["dublincore.ui"] = new WdgDublinCore(this); widgets["exif.ui"] = new WdgExif(this); Q_FOREACH (const QString & file, files) { QFile xmlFile(file); xmlFile.open(QFile::ReadOnly); QString errMsg; int errLine, errCol; QDomDocument document; if (!document.setContent(&xmlFile, false, &errMsg, &errLine, &errCol)) { dbgMetaData << "Error reading XML at line" << errLine << " column" << errCol << " :" << errMsg; } QDomElement rootElement = document.documentElement(); if (rootElement.tagName() != "MetaDataEditor") { dbgMetaData << "Invalid XML file"; } const QString uiFileName = rootElement.attribute("uiFile"); const QString pageName = i18nc("metadata editor page", rootElement.attribute("name").toUtf8()); const QString iconName = rootElement.attribute("icon"); if (uiFileName == "") continue; QWidget *widget = widgets[uiFileName]; QDomNodeList list = rootElement.childNodes(); const int size = list.size(); for (int i = 0; i < size; ++i) { QDomElement elem = list.item(i).toElement(); if (elem.isNull() || elem.tagName() != "EntryEditor") continue; const QString editorName = elem.attribute("editorName"); const QString schemaUri = elem.attribute("schemaUri"); const QString entryName = elem.attribute("entryName"); const QString editorSignal = '2' + elem.attribute("editorSignal"); const QString propertyName = elem.attribute("propertyName"); const QString structureField = elem.attribute("structureField"); - bool ok; - int arrayIndex = elem.attribute("arrayIndex", "-1").toInt(&ok); - if (!ok) arrayIndex = -1; + int arrayIndex = 0; + if (elem.hasAttribute("arrayIndex")) { + bool ok; + arrayIndex = elem.attribute("arrayIndex", "0").toInt(&ok); + if (!ok) arrayIndex = 0; + } dbgMetaData << ppVar(editorName) << ppVar(arrayIndex); - QWidget* obj = widget->findChild(editorName); - if (obj) { + QWidget* metaDataEditorPage = widget->findChild(editorName); + if (metaDataEditorPage) { const KisMetaData::Schema* schema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(schemaUri); if (schema) { if (!d->store->containsEntry(schema, entryName)) { dbgMetaData << " Store does not have yet entry :" << entryName << " in" << schemaUri << " ==" << schema->generateQualifiedName(entryName); } QString key = schema->generateQualifiedName(entryName); - KisEntryEditor* ee = new KisEntryEditor(obj, d->store, key, propertyName, structureField, arrayIndex); - connect(obj, editorSignal.toLatin1(), ee, SLOT(valueEdited())); + KisEntryEditor* ee = new KisEntryEditor(metaDataEditorPage, d->store, key, propertyName, structureField, arrayIndex); + connect(metaDataEditorPage, editorSignal.toLatin1(), ee, SLOT(valueEdited())); QList otherEditors = d->entryEditors.values(key); Q_FOREACH (KisEntryEditor* oe, otherEditors) { connect(ee, SIGNAL(valueHasBeenEdited()), oe, SLOT(valueChanged())); connect(oe, SIGNAL(valueHasBeenEdited()), ee, SLOT(valueChanged())); } d->entryEditors.insert(key, ee); } else { dbgMetaData << "Unknown schema :" << schemaUri; } } else { dbgMetaData << "Unknown object :" << editorName; } } xmlFile.close(); KPageWidgetItem *page = new KPageWidgetItem(widget, pageName); if (!iconName.isEmpty()) { page->setIcon(KisIconUtils::loadIcon(iconName)); } addPage(page); } // Add the list page QTableView* tableView = new QTableView; KisMetaDataModel* model = new KisMetaDataModel(d->store); tableView->setModel(model); tableView->verticalHeader()->setVisible(false); tableView->resizeColumnsToContents(); KPageWidgetItem *page = new KPageWidgetItem(tableView, i18n("List")); page->setIcon(KisIconUtils::loadIcon("format-list-unordered")); addPage(page); } KisMetaDataEditor::~KisMetaDataEditor() { Q_FOREACH (KisEntryEditor* e, d->entryEditors) { delete e; } delete d->store; delete d; } void KisMetaDataEditor::accept() { KPageDialog::accept(); d->originalStore->copyFrom(d->store); } diff --git a/plugins/extensions/metadataeditor/kis_meta_data_model.cpp b/plugins/extensions/metadataeditor/kis_meta_data_model.cpp index c06f10fc14..ea8af1cb86 100644 --- a/plugins/extensions/metadataeditor/kis_meta_data_model.cpp +++ b/plugins/extensions/metadataeditor/kis_meta_data_model.cpp @@ -1,115 +1,115 @@ /* * Copyright (c) 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; 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 "kis_meta_data_model.h" #include -#include -#include -#include +#include +#include +#include KisMetaDataModel::KisMetaDataModel(KisMetaData::Store* store) : m_store(store) { } int KisMetaDataModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_store->keys().count(); } int KisMetaDataModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 3; } QVariant KisMetaDataModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } Q_ASSERT(index.row() < m_store->keys().count()); switch (role) { case Qt::DisplayRole: { switch (index.column()) { case 0: return m_store->keys()[index.row()]; case 1: { KisMetaData::Value::ValueType vt = m_store->entries()[index.row()].value().type(); switch (vt) { case KisMetaData::Value::Invalid: return i18n("Invalid"); case KisMetaData::Value::Variant: { int vt = m_store->entries()[index.row()].value().asVariant().type(); switch (vt) { case QVariant::Date: case QVariant::DateTime: return i18n("Date"); case QVariant::Double: case QVariant::Int: return i18n("Number"); case QVariant::String: return i18n("String"); default: return i18n("Variant (%1)", vt); } } case KisMetaData::Value::OrderedArray: return i18n("Ordered array"); case KisMetaData::Value::UnorderedArray: return i18n("Unordered array"); case KisMetaData::Value::AlternativeArray: return i18n("Alternative array"); case KisMetaData::Value::LangArray: return i18n("Language array"); case KisMetaData::Value::Structure: return i18n("Structure"); case KisMetaData::Value::Rational: return i18n("Rational"); } break; } case 2: return m_store->entries()[index.row()].value().toString(); } break; } default: return QVariant(); } return QVariant(); } QVariant KisMetaDataModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { Q_ASSERT(section < 3); switch (section) { case 0: return i18n("Key"); case 1: return i18n("Type"); case 2: return i18nc("Metadata item value", "Value"); } } return QVariant(); } diff --git a/plugins/extensions/metadataeditor/metadataeditor.cc b/plugins/extensions/metadataeditor/metadataeditor.cc index 8504614ff6..d610b5ac44 100644 --- a/plugins/extensions/metadataeditor/metadataeditor.cc +++ b/plugins/extensions/metadataeditor/metadataeditor.cc @@ -1,69 +1,69 @@ /* * 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; 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 "metadataeditor.h" #include #include #include #include #include #include #include "kis_config.h" #include "kis_cursor.h" #include "kis_global.h" #include "kis_layer.h" #include "kis_node_manager.h" #include "kis_types.h" #include "KisViewManager.h" #include "kis_action.h" #include "kis_image.h" -#include -#include -#include -#include +#include +#include +#include +#include #include "kis_entry_editor.h" #include "kis_meta_data_editor.h" K_PLUGIN_FACTORY_WITH_JSON(metadataeditorPluginFactory, "kritametadataeditor.json", registerPlugin();) metadataeditorPlugin::metadataeditorPlugin(QObject *parent, const QVariantList &) : KisActionPlugin(parent) { KisAction *action = createAction("EditLayerMetaData"); connect(action, SIGNAL(triggered()), this, SLOT(slotEditLayerMetaData())); } metadataeditorPlugin::~metadataeditorPlugin() { } void metadataeditorPlugin::slotEditLayerMetaData() { KisImageWSP image = viewManager()->image(); if (!image) return; KisMetaDataEditor editor(viewManager()->mainWindow(), viewManager()->nodeManager()->activeLayer()->metaData()); editor.exec(); } #include "metadataeditor.moc" diff --git a/plugins/extensions/pykrita/sip/krita/Node.sip b/plugins/extensions/pykrita/sip/krita/Node.sip index fdd535229e..b52cb24d5c 100644 --- a/plugins/extensions/pykrita/sip/krita/Node.sip +++ b/plugins/extensions/pykrita/sip/krita/Node.sip @@ -1,65 +1,65 @@ class Node : QObject { %TypeHeaderCode #include "Node.h" %End Node(const Node & __0); public: virtual ~Node(); bool operator==(const Node &other) const; bool operator!=(const Node &other) const; public Q_SLOTS: //QList shapes() const /Factory/; Node *clone() const /Factory/; bool alphaLocked() const; void setAlphaLocked(bool value); QString blendingMode() const; void setBlendingMode(QString value); QList channels() const /Factory/; QList childNodes() const /Factory/; bool addChildNode(Node *child, Node *above); bool removeChildNode(Node *child); void setChildNodes(QList nodes); QString colorDepth() const; bool animated() const; void enableAnimation() const; void setCollapsed(bool collapsed); bool collapsed() const; int colorLabel() const; void setColorLabel(int value); QString colorModel() const; QString colorProfile() const; bool setColorProfile(const QString &colorProfile); bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); bool inheritAlpha() const; void setInheritAlpha(bool value); bool locked() const; void setLocked(bool value); QString name() const; void setName(QString value); int opacity() const; void setOpacity(int value); Node * parentNode() const /Factory/; QString type() const; QIcon icon() const; bool visible() const; void setVisible(bool value); QByteArray pixelData(int x, int y, int w, int h) const; QByteArray pixelDataAtTime(int x, int y, int w, int h, int time) const; QByteArray projectionPixelData(int x, int y, int w, int h) const; void setPixelData(QByteArray value, int x, int y, int w, int h); QRect bounds() const; void move(int x, int y); QPoint position() const; bool remove(); Node *duplicate() /Factory/; void save(const QString &filename, double xRes, double yRes); Node *mergeDown() /Factory/; - void scaleNode(int width, int height, QString strategy); + void scaleNode(const QPointF &origin, int width, int height, QString strategy); void rotateNode(double radians); void cropNode(int x, int y, int w, int h); void shearNode(double angleX, double angleY); QImage thumbnail(int w, int h); Q_SIGNALS: private: }; diff --git a/plugins/extensions/rotateimage/rotateimage.cc b/plugins/extensions/rotateimage/rotateimage.cc index 97dd9bdeca..795060f92f 100644 --- a/plugins/extensions/rotateimage/rotateimage.cc +++ b/plugins/extensions/rotateimage/rotateimage.cc @@ -1,148 +1,229 @@ /* * rotateimage.cc -- Part of Krita * * Copyright (c) 2004 Michael Thaler * * 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 "rotateimage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include "dlg_rotateimage.h" K_PLUGIN_FACTORY_WITH_JSON(RotateImageFactory, "kritarotateimage.json", registerPlugin();) RotateImage::RotateImage(QObject *parent, const QVariantList &) : KisActionPlugin(parent) { KisAction *action = createAction("rotateimage"); connect(action, SIGNAL(triggered()), this, SLOT(slotRotateImage())); action = createAction("rotateImageCW90"); connect(action, SIGNAL(triggered()), this, SLOT(slotRotateImage90())); action = createAction("rotateImage180"); connect(action, SIGNAL(triggered()), this, SLOT(slotRotateImage180())); action = createAction("rotateImageCCW90"); connect(action, SIGNAL(triggered()), this, SLOT(slotRotateImage270())); action = createAction("mirrorImageHorizontal"); connect(action, SIGNAL(triggered()), this, SLOT(slotMirrorImageHorizontal())); action = createAction("mirrorImageVertical"); connect(action, SIGNAL(triggered()), this, SLOT(slotMirrorImageVertical())); action = createAction("rotatelayer"); connect(action, SIGNAL(triggered()), this, SLOT(slotRotateLayer())); action = createAction("rotateLayer180"); - connect(action, SIGNAL(triggered()), viewManager()->nodeManager(), SLOT(rotate180())); + connect(action, SIGNAL(triggered()), this, SLOT(slotRotateLayer180())); action = createAction("rotateLayerCW90"); - connect(action, SIGNAL(triggered()), viewManager()->nodeManager(), SLOT(rotateRight90())); + connect(action, SIGNAL(triggered()), this, SLOT(slotRotateLayerCW90())); action = createAction("rotateLayerCCW90"); - connect(action, SIGNAL(triggered()), viewManager()->nodeManager(), SLOT(rotateLeft90())); + connect(action, SIGNAL(triggered()), this, SLOT(slotRotateLayerCCW90())); + + action = createAction("rotateAllLayers"); + connect(action, SIGNAL(triggered()), this, SLOT(slotRotateAllLayers())); + + action = createAction("rotateAllLayersCW90"); + connect(action, SIGNAL(triggered()), this, SLOT(slotRotateAllLayersCW90())); + + action = createAction("rotateAllLayersCCW90"); + connect(action, SIGNAL(triggered()), this, SLOT(slotRotateAllLayersCCW90())); + + action = createAction("rotateAllLayers180"); + connect(action, SIGNAL(triggered()), this, SLOT(slotRotateAllLayers180())); } RotateImage::~RotateImage() { } void RotateImage::slotRotateImage() { KisImageWSP image = viewManager()->image(); - if (!image) return; + if (!viewManager()->blockUntilOperationsFinished(image)) return; + DlgRotateImage * dlgRotateImage = new DlgRotateImage(viewManager()->mainWindow(), "RotateImage"); Q_CHECK_PTR(dlgRotateImage); dlgRotateImage->setCaption(i18n("Rotate Image")); if (dlgRotateImage->exec() == QDialog::Accepted) { double angle = dlgRotateImage->angle() * M_PI / 180; viewManager()->imageManager()->rotateCurrentImage(angle); } delete dlgRotateImage; } void RotateImage::slotRotateImage90() { viewManager()->imageManager()->rotateCurrentImage(M_PI / 2); } void RotateImage::slotRotateImage180() { viewManager()->imageManager()->rotateCurrentImage(M_PI); } void RotateImage::slotRotateImage270() { viewManager()->imageManager()->rotateCurrentImage(- M_PI / 2 + M_PI*2); } void RotateImage::slotMirrorImageVertical() { KisImageWSP image = viewManager()->image(); if (!image) return; - viewManager()->nodeManager()->mirrorNode(image->rootLayer(), kundo2_i18n("Mirror Image Vertically"), Qt::Vertical); + viewManager()->nodeManager()->mirrorNode(image->rootLayer(), + kundo2_i18n("Mirror Image Vertically"), + Qt::Vertical, 0); } void RotateImage::slotMirrorImageHorizontal() { KisImageWSP image = viewManager()->image(); if (!image) return; - viewManager()->nodeManager()->mirrorNode(image->rootLayer(), kundo2_i18n("Mirror Image Horizontally"), Qt::Horizontal); + viewManager()->nodeManager()->mirrorNode(image->rootLayer(), + kundo2_i18n("Mirror Image Horizontally"), + Qt::Horizontal, 0); } -void RotateImage::slotRotateLayer() + +void RotateImage::rotateLayerCustomImpl(KisNodeSP rootNode) { KisImageWSP image = viewManager()->image(); - if (!image) return; + if (!viewManager()->blockUntilOperationsFinished(image)) return; + DlgRotateImage * dlgRotateImage = new DlgRotateImage(viewManager()->mainWindow(), "RotateLayer"); Q_CHECK_PTR(dlgRotateImage); dlgRotateImage->setCaption(i18n("Rotate Layer")); if (dlgRotateImage->exec() == QDialog::Accepted) { double angle = dlgRotateImage->angle() * M_PI / 180; - viewManager()->nodeManager()->rotate(angle); - + image->rotateNode(rootNode, angle, viewManager()->selection()); } delete dlgRotateImage; } +void RotateImage::rotateLayerImpl(KisNodeSP rootNode, qreal radians) +{ + KisImageWSP image = viewManager()->image(); + if (!image) return; + + if (!viewManager()->blockUntilOperationsFinished(image)) return; + + image->rotateNode(rootNode, radians, viewManager()->selection()); +} + +void RotateImage::slotRotateLayer() +{ + rotateLayerCustomImpl(viewManager()->activeLayer()); +} + +void RotateImage::slotRotateAllLayers() +{ + KisImageWSP image = viewManager()->image(); + if (!image) return; + + rotateLayerCustomImpl(image->root()); +} + +void RotateImage::slotRotateLayerCW90() +{ + rotateLayerImpl(viewManager()->activeLayer(), M_PI / 2); +} + +void RotateImage::slotRotateLayerCCW90() +{ + rotateLayerImpl(viewManager()->activeLayer(), -M_PI / 2); +} + +void RotateImage::slotRotateLayer180() +{ + rotateLayerImpl(viewManager()->activeLayer(), M_PI); +} + +void RotateImage::slotRotateAllLayersCW90() +{ + KisImageWSP image = viewManager()->image(); + if (!image) return; + + rotateLayerImpl(image->root(), M_PI / 2); +} + +void RotateImage::slotRotateAllLayersCCW90() +{ + KisImageWSP image = viewManager()->image(); + if (!image) return; + + rotateLayerImpl(image->root(), -M_PI / 2); +} + +void RotateImage::slotRotateAllLayers180() +{ + KisImageWSP image = viewManager()->image(); + if (!image) return; + + rotateLayerImpl(image->root(), M_PI); +} + #include "rotateimage.moc" diff --git a/plugins/extensions/rotateimage/rotateimage.h b/plugins/extensions/rotateimage/rotateimage.h index b0e88dcb23..22e4d5e801 100644 --- a/plugins/extensions/rotateimage/rotateimage.h +++ b/plugins/extensions/rotateimage/rotateimage.h @@ -1,45 +1,57 @@ /* * rotateimage.h -- Part of Krita * * Copyright (c) 2004 Michael Thaler (michael.thaler@physik.tu-muenchen.de) * * 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 ROTATEIMAGE_H #define ROTATEIMAGE_H #include #include +#include "kis_types.h" class RotateImage : public KisActionPlugin { Q_OBJECT public: RotateImage(QObject *parent, const QVariantList &); ~RotateImage() override; +private: + void rotateLayerCustomImpl(KisNodeSP rootNode); + void rotateLayerImpl(KisNodeSP rootNode, qreal radians); + private Q_SLOTS: void slotRotateImage(); void slotRotateImage90(); void slotRotateImage180(); void slotRotateImage270(); void slotMirrorImageVertical(); void slotMirrorImageHorizontal(); void slotRotateLayer(); + void slotRotateLayerCW90(); + void slotRotateLayerCCW90(); + void slotRotateLayer180(); + void slotRotateAllLayers(); + void slotRotateAllLayersCW90(); + void slotRotateAllLayersCCW90(); + void slotRotateAllLayers180(); }; #endif // ROTATEIMAGE_H diff --git a/plugins/extensions/shearimage/shearimage.cc b/plugins/extensions/shearimage/shearimage.cc index ff009807d2..a89bca0d11 100644 --- a/plugins/extensions/shearimage/shearimage.cc +++ b/plugins/extensions/shearimage/shearimage.cc @@ -1,89 +1,110 @@ /* * shearimage.cc -- Part of Krita * * Copyright (c) 2004 Michael Thaler * * 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 "shearimage.h" #include #include #include #include #include #include #include +#include "kis_selection.h" #include "dlg_shearimage.h" K_PLUGIN_FACTORY_WITH_JSON(ShearImageFactory, "kritashearimage.json", registerPlugin();) ShearImage::ShearImage(QObject *parent, const QVariantList &) : KisActionPlugin(parent) { KisAction *action = createAction("shearimage"); connect(action, SIGNAL(triggered()), this, SLOT(slotShearImage())); action = createAction("shearlayer"); connect(action, SIGNAL(triggered()), this, SLOT(slotShearLayer())); + + action = createAction("shearAllLayers"); + connect(action, SIGNAL(triggered()), this, SLOT(slotShearAllLayers())); } ShearImage::~ShearImage() { } void ShearImage::slotShearImage() { KisImageWSP image = viewManager()->image(); - if (!image) return; + if (!viewManager()->blockUntilOperationsFinished(image)) return; + DlgShearImage * dlgShearImage = new DlgShearImage(viewManager()->mainWindow(), "ShearImage"); Q_CHECK_PTR(dlgShearImage); dlgShearImage->setCaption(i18n("Shear Image")); if (dlgShearImage->exec() == QDialog::Accepted) { qint32 angleX = dlgShearImage->angleX(); qint32 angleY = dlgShearImage->angleY(); viewManager()->imageManager()->shearCurrentImage(angleX, angleY); } delete dlgShearImage; } -void ShearImage::slotShearLayer() +void ShearImage::shearLayerImpl(KisNodeSP rootNode) { KisImageWSP image = viewManager()->image(); - if (!image) return; + if (!viewManager()->blockUntilOperationsFinished(image)) return; + DlgShearImage * dlgShearImage = new DlgShearImage(viewManager()->mainWindow(), "ShearLayer"); Q_CHECK_PTR(dlgShearImage); dlgShearImage->setCaption(i18n("Shear Layer")); if (dlgShearImage->exec() == QDialog::Accepted) { qint32 angleX = dlgShearImage->angleX(); qint32 angleY = dlgShearImage->angleY(); - viewManager()->nodeManager()->shear(angleX, angleY); + image->shearNode(rootNode, + angleX, angleY, + viewManager()->selection()); } delete dlgShearImage; } +void ShearImage::slotShearLayer() +{ + shearLayerImpl(viewManager()->activeNode()); +} + +void ShearImage::slotShearAllLayers() +{ + KisImageWSP image = viewManager()->image(); + if (!image) return; + + shearLayerImpl(image->root()); +} + #include "shearimage.moc" diff --git a/plugins/extensions/shearimage/shearimage.h b/plugins/extensions/shearimage/shearimage.h index 4948724513..452958849c 100644 --- a/plugins/extensions/shearimage/shearimage.h +++ b/plugins/extensions/shearimage/shearimage.h @@ -1,40 +1,46 @@ /* * shearimage.h -- Part of Krita * * Copyright (c) 2004 Michael Thaler (michael.thaler@physik.tu-muenchen.de) * * 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 SHEARIMAGE_H #define SHEARIMAGE_H #include #include +#include "kis_types.h" + class ShearImage : public KisActionPlugin { Q_OBJECT public: ShearImage(QObject *parent, const QVariantList &); ~ShearImage() override; +private: + void shearLayerImpl(KisNodeSP rootNode); + private Q_SLOTS: void slotShearImage(); void slotShearLayer(); + void slotShearAllLayers(); }; #endif // SHEARIMAGE_H diff --git a/plugins/impex/exr/exr_converter.cc b/plugins/impex/exr/exr_converter.cc index a0f8099624..b0dc356df1 100644 --- a/plugins/impex/exr/exr_converter.cc +++ b/plugins/impex/exr/exr_converter.cc @@ -1,1354 +1,1354 @@ /* * Copyright (c) 2005 Adrian Page * Copyright (c) 2010 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 "exr_converter.h" #include #include #include #include #include #include #include "exr_extra_tags.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "kis_kra_savexml_visitor.h" // Do not translate! #define HDR_LAYER "HDR Layer" template struct Rgba { _T_ r; _T_ g; _T_ b; _T_ a; }; struct ExrGroupLayerInfo; struct ExrLayerInfoBase { ExrLayerInfoBase() : colorSpace(0), parent(0) { } const KoColorSpace* colorSpace; QString name; const ExrGroupLayerInfo* parent; }; struct ExrGroupLayerInfo : public ExrLayerInfoBase { ExrGroupLayerInfo() : groupLayer(0) {} KisGroupLayerSP groupLayer; }; enum ImageType { IT_UNKNOWN, IT_FLOAT16, IT_FLOAT32, IT_UNSUPPORTED }; struct ExrPaintLayerInfo : public ExrLayerInfoBase { ExrPaintLayerInfo() : imageType(IT_UNKNOWN) { } ImageType imageType; QMap< QString, QString> channelMap; ///< first is either R, G, B or A second is the EXR channel name struct Remap { Remap(const QString& _original, const QString& _current) : original(_original), current(_current) { } QString original; QString current; }; QList< Remap > remappedChannels; ///< this is used to store in the metadata the mapping between exr channel name, and channels used in Krita void updateImageType(ImageType channelType); }; void ExrPaintLayerInfo::updateImageType(ImageType channelType) { if (imageType == IT_UNKNOWN) { imageType = channelType; } else if (imageType != channelType) { imageType = IT_UNSUPPORTED; } } struct ExrPaintLayerSaveInfo { QString name; ///< name of the layer with a "." at the end (ie "group1.group2.layer1.") KisPaintLayerSP layer; QList channels; Imf::PixelType pixelType; }; struct EXRConverter::Private { Private() : doc(0) , alphaWasModified(false) , showNotifications(false) {} KisImageSP image; KisDocument *doc; bool alphaWasModified; bool showNotifications; QString errorMessage; template void unmultiplyAlpha(typename WrapperType::pixel_type *pixel); template void decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype); template void decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype); QDomDocument loadExtraLayersInfo(const Imf::Header &header); bool checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set exrLayerNames); void makeLayerNamesUnique(QList& informationObjects); void recBuildPaintLayerSaveInfo(QList& informationObjects, const QString& name, KisGroupLayerSP parent); void reportLayersNotSaved(const QSet &layersNotSaved); QString fetchExtraLayersInfo(QList& informationObjects); }; EXRConverter::EXRConverter(KisDocument *doc, bool showNotifications) : d(new Private) { d->doc = doc; d->showNotifications = showNotifications; // Set thread count for IlmImf library Imf::setGlobalThreadCount(QThread::idealThreadCount()); dbgFile << "EXR Threadcount was set to: " << QThread::idealThreadCount(); } EXRConverter::~EXRConverter() { } ImageType imfTypeToKisType(Imf::PixelType type) { switch (type) { case Imf::UINT: case Imf::NUM_PIXELTYPES: return IT_UNSUPPORTED; case Imf::HALF: return IT_FLOAT16; case Imf::FLOAT: return IT_FLOAT32; default: qFatal("Out of bound enum"); return IT_UNKNOWN; } } const KoColorSpace* kisTypeToColorSpace(QString model, ImageType imageType) { switch (imageType) { case IT_FLOAT16: return KoColorSpaceRegistry::instance()->colorSpace(model, Float16BitsColorDepthID.id(), ""); case IT_FLOAT32: return KoColorSpaceRegistry::instance()->colorSpace(model, Float32BitsColorDepthID.id(), ""); case IT_UNKNOWN: case IT_UNSUPPORTED: return 0; default: qFatal("Out of bound enum"); return 0; } } template static inline T alphaEpsilon() { return static_cast(HALF_EPSILON); } template static inline T alphaNoiseThreshold() { return static_cast(0.01); // 1% } template struct RgbPixelWrapper { typedef T channel_type; typedef Rgba pixel_type; RgbPixelWrapper(Rgba &_pixel) : pixel(_pixel) {} inline T alpha() const { return pixel.a; } inline bool checkMultipliedColorsConsistent() const { return !(pixel.a < alphaEpsilon() && (pixel.r > 0.0 || pixel.g > 0.0 || pixel.b > 0.0)); } inline bool checkUnmultipliedColorsConsistent(const Rgba &mult) const { const T alpha = pixel.a; return abs(alpha) >= alphaNoiseThreshold() || (pixel.r * alpha == mult.r && pixel.g * alpha == mult.g && pixel.b * alpha == mult.b); } inline void setUnmultiplied(const Rgba &mult, qreal newAlpha) { pixel.r = mult.r / newAlpha; pixel.g = mult.g / newAlpha; pixel.b = mult.b / newAlpha; pixel.a = newAlpha; } Rgba &pixel; }; template struct GrayPixelWrapper { typedef T channel_type; typedef typename KoGrayTraits::Pixel pixel_type; GrayPixelWrapper(pixel_type &_pixel) : pixel(_pixel) {} inline T alpha() const { return pixel.alpha; } inline bool checkMultipliedColorsConsistent() const { return !(pixel.alpha < alphaEpsilon() && pixel.gray > 0.0); } inline bool checkUnmultipliedColorsConsistent(const pixel_type &mult) const { const T alpha = pixel.alpha; return alpha >= alphaNoiseThreshold() || pixel.gray * alpha == mult.gray; } inline void setUnmultiplied(const pixel_type &mult, qreal newAlpha) { pixel.gray = mult.gray / newAlpha; pixel.alpha = newAlpha; } pixel_type &pixel; }; template void EXRConverter::Private::unmultiplyAlpha(typename WrapperType::pixel_type *pixel) { typedef typename WrapperType::pixel_type pixel_type; typedef typename WrapperType::channel_type channel_type; WrapperType srcPixel(*pixel); if (!srcPixel.checkMultipliedColorsConsistent()) { channel_type newAlpha = srcPixel.alpha(); pixel_type __dstPixelData; WrapperType dstPixel(__dstPixelData); /** * Division by a tiny alpha may result in an overflow of half * value. That is why we use safe iterational approach. */ while (1) { dstPixel.setUnmultiplied(srcPixel.pixel, newAlpha); if (dstPixel.checkUnmultipliedColorsConsistent(srcPixel.pixel)) { break; } newAlpha += alphaEpsilon(); alphaWasModified = true; } *pixel = dstPixel.pixel; } else if (srcPixel.alpha() > 0.0) { srcPixel.setUnmultiplied(srcPixel.pixel, srcPixel.alpha()); } } template void multiplyAlpha(Pixel *pixel) { if (alphaPos >= 0) { T alpha = pixel->data[alphaPos]; if (alpha > 0.0) { for (int i = 0; i < size; ++i) { if (i != alphaPos) { pixel->data[i] *= alpha; } } pixel->data[alphaPos] = alpha; } } } template void EXRConverter::Private::decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype) { typedef Rgba<_T_> Rgba; QVector pixels(width * height); bool hasAlpha = info.channelMap.contains("A"); Imf::FrameBuffer frameBuffer; Rgba* frameBufferData = (pixels.data()) - xstart - ystart * width; frameBuffer.insert(info.channelMap["R"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->r, sizeof(Rgba) * 1, sizeof(Rgba) * width)); frameBuffer.insert(info.channelMap["G"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->g, sizeof(Rgba) * 1, sizeof(Rgba) * width)); frameBuffer.insert(info.channelMap["B"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->b, sizeof(Rgba) * 1, sizeof(Rgba) * width)); if (hasAlpha) { frameBuffer.insert(info.channelMap["A"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->a, sizeof(Rgba) * 1, sizeof(Rgba) * width)); } file.setFrameBuffer(frameBuffer); file.readPixels(ystart, height + ystart - 1); Rgba *rgba = pixels.data(); QRect paintRegion(xstart, ystart, width, height); KisSequentialIterator it(layer->paintDevice(), paintRegion); while (it.nextPixel()) { if (hasAlpha) { unmultiplyAlpha >(rgba); } typename KoRgbTraits<_T_>::Pixel* dst = reinterpret_cast::Pixel*>(it.rawData()); dst->red = rgba->r; dst->green = rgba->g; dst->blue = rgba->b; if (hasAlpha) { dst->alpha = rgba->a; } else { dst->alpha = 1.0; } ++rgba; } } template void EXRConverter::Private::decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype) { typedef typename GrayPixelWrapper<_T_>::channel_type channel_type; typedef typename GrayPixelWrapper<_T_>::pixel_type pixel_type; KIS_ASSERT_RECOVER_RETURN( layer->paintDevice()->colorSpace()->colorModelId() == GrayAColorModelID); QVector pixels(width * height); Q_ASSERT(info.channelMap.contains("G")); dbgFile << "G -> " << info.channelMap["G"]; bool hasAlpha = info.channelMap.contains("A"); dbgFile << "Has Alpha:" << hasAlpha; Imf::FrameBuffer frameBuffer; pixel_type* frameBufferData = (pixels.data()) - xstart - ystart * width; frameBuffer.insert(info.channelMap["G"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->gray, sizeof(pixel_type) * 1, sizeof(pixel_type) * width)); if (hasAlpha) { frameBuffer.insert(info.channelMap["A"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->alpha, sizeof(pixel_type) * 1, sizeof(pixel_type) * width)); } file.setFrameBuffer(frameBuffer); file.readPixels(ystart, height + ystart - 1); pixel_type *srcPtr = pixels.data(); QRect paintRegion(xstart, ystart, width, height); KisSequentialIterator it(layer->paintDevice(), paintRegion); do { if (hasAlpha) { unmultiplyAlpha >(srcPtr); } pixel_type* dstPtr = reinterpret_cast(it.rawData()); dstPtr->gray = srcPtr->gray; dstPtr->alpha = hasAlpha ? srcPtr->alpha : channel_type(1.0); ++srcPtr; } while (it.nextPixel()); } bool recCheckGroup(const ExrGroupLayerInfo& group, QStringList list, int idx1, int idx2) { if (idx1 > idx2) return true; if (group.name == list[idx2]) { return recCheckGroup(*group.parent, list, idx1, idx2 - 1); } return false; } ExrGroupLayerInfo* searchGroup(QList* groups, QStringList list, int idx1, int idx2) { if (idx1 > idx2) { return 0; } // Look for the group for (int i = 0; i < groups->size(); ++i) { if (recCheckGroup(groups->at(i), list, idx1, idx2)) { return &(*groups)[i]; } } // Create the group ExrGroupLayerInfo info; info.name = list.at(idx2); info.parent = searchGroup(groups, list, idx1, idx2 - 1); groups->append(info); return &groups->last(); } QDomDocument EXRConverter::Private::loadExtraLayersInfo(const Imf::Header &header) { const Imf::StringAttribute *layersInfoAttribute = header.findTypedAttribute(EXR_KRITA_LAYERS); if (!layersInfoAttribute) return QDomDocument(); QString layersInfoString = QString::fromUtf8(layersInfoAttribute->value().c_str()); QDomDocument doc; doc.setContent(layersInfoString); return doc; } bool EXRConverter::Private::checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set exrLayerNames) { std::set extraInfoLayers; QDomElement root = doc.documentElement(); KIS_ASSERT_RECOVER(!root.isNull() && root.hasChildNodes()) { return false; }; QDomElement el = root.firstChildElement(); while(!el.isNull()) { KIS_ASSERT_RECOVER(el.hasAttribute(EXR_NAME)) { return false; }; QString layerName = el.attribute(EXR_NAME).toUtf8(); if (layerName != QString(HDR_LAYER)) { extraInfoLayers.insert(el.attribute(EXR_NAME).toUtf8().constData()); } el = el.nextSiblingElement(); } bool result = (extraInfoLayers == exrLayerNames); if (!result) { dbgKrita << "WARINING: Krita EXR extra layers info is inconsistent!"; dbgKrita << ppVar(extraInfoLayers.size()) << ppVar(exrLayerNames.size()); std::set::const_iterator it1 = extraInfoLayers.begin(); std::set::const_iterator it2 = exrLayerNames.begin(); std::set::const_iterator end1 = extraInfoLayers.end(); for (; it1 != end1; ++it1, ++it2) { dbgKrita << it1->c_str() << it2->c_str(); } } return result; } KisImageBuilder_Result EXRConverter::decode(const QString &filename) { Imf::InputFile file(QFile::encodeName(filename)); Imath::Box2i dw = file.header().dataWindow(); Imath::Box2i displayWindow = file.header().displayWindow(); int width = dw.max.x - dw.min.x + 1; int height = dw.max.y - dw.min.y + 1; int dx = dw.min.x; int dy = dw.min.y; // Display the attributes of a file for (Imf::Header::ConstIterator it = file.header().begin(); it != file.header().end(); ++it) { dbgFile << "Attribute: " << it.name() << " type: " << it.attribute().typeName(); } // fetch Krita's extra layer info, which might have been stored previously QDomDocument extraLayersInfo = d->loadExtraLayersInfo(file.header()); // Construct the list of LayerInfo QList informationObjects; QList groups; ImageType imageType = IT_UNKNOWN; const Imf::ChannelList &channels = file.header().channels(); std::set layerNames; channels.layers(layerNames); if (!extraLayersInfo.isNull() && !d->checkExtraLayersInfoConsistent(extraLayersInfo, layerNames)) { // it is inconsistent anyway extraLayersInfo = QDomDocument(); } // Check if there are A, R, G, B channels dbgFile << "Checking for ARGB channels, they can occur in single-layer _or_ multi-layer images:"; ExrPaintLayerInfo info; bool topLevelRGBFound = false; info.name = HDR_LAYER; QStringList topLevelChannelNames = QStringList() << "A" << "R" << "G" << "B" << ".A" << ".R" << ".G" << ".B" << "A." << "R." << "G." << "B." << "A." << "R." << "G." << "B." << ".alpha" << ".red" << ".green" << ".blue"; for (Imf::ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) { const Imf::Channel &channel = i.channel(); dbgFile << "Channel name = " << i.name() << " type = " << channel.type; QString qname = i.name(); if (topLevelChannelNames.contains(qname)) { topLevelRGBFound = true; dbgFile << "Found top-level channel" << qname; info.channelMap[qname] = qname; info.updateImageType(imfTypeToKisType(channel.type)); } // Channel names that don't contain a "." or that contain a // "." only at the beginning or at the end are not considered // to be part of any layer. else if (!qname.contains('.') || !qname.mid(1).contains('.') || !qname.left(qname.size() - 1).contains('.')) { warnFile << "Found a top-level channel that is not part of the rendered image" << qname << ". Krita will not load this channel."; } } if (topLevelRGBFound) { dbgFile << "Toplevel layer" << info.name << ":Image type:" << imageType << "Layer type" << info.imageType; informationObjects.push_back(info); imageType = info.imageType; } dbgFile << "Extra layers:" << layerNames.size(); for (std::set::const_iterator i = layerNames.begin();i != layerNames.end(); ++i) { info = ExrPaintLayerInfo(); dbgFile << "layer name = " << i->c_str(); info.name = i->c_str(); Imf::ChannelList::ConstIterator layerBegin, layerEnd; channels.channelsInLayer(*i, layerBegin, layerEnd); for (Imf::ChannelList::ConstIterator j = layerBegin; j != layerEnd; ++j) { const Imf::Channel &channel = j.channel(); info.updateImageType(imfTypeToKisType(channel.type)); QString qname = j.name(); QStringList list = qname.split('.'); QString layersuffix = list.last(); dbgFile << "\tchannel " << j.name() << "suffix" << layersuffix << " type = " << channel.type; // Nuke writes the channels for sublayers as .red instead of .R, so convert those. // See https://bugs.kde.org/show_bug.cgi?id=393771 if (topLevelChannelNames.contains("." + layersuffix)) { layersuffix = layersuffix.at(0).toUpper(); } dbgFile << "\t\tsuffix" << layersuffix; if (list.size() > 1) { info.name = list[list.size()-2]; info.parent = searchGroup(&groups, list, 0, list.size() - 3); } info.channelMap[layersuffix] = qname; } if (info.imageType != IT_UNKNOWN && info.imageType != IT_UNSUPPORTED) { informationObjects.push_back(info); if (imageType < info.imageType) { imageType = info.imageType; } } } dbgFile << "File has" << informationObjects.size() << "layer(s)"; // Set the colorspaces for (int i = 0; i < informationObjects.size(); ++i) { ExrPaintLayerInfo& info = informationObjects[i]; QString modelId; if (info.channelMap.size() == 1) { modelId = GrayAColorModelID.id(); QString key = info.channelMap.begin().key(); if (key != "G") { info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(key, "G")); QString channel = info.channelMap.begin().value(); info.channelMap.clear(); info.channelMap["G"] = channel; } } else if (info.channelMap.size() == 2) { modelId = GrayAColorModelID.id(); QMap::const_iterator it = info.channelMap.constBegin(); QMap::const_iterator end = info.channelMap.constEnd(); QString failingChannelKey; for (; it != end; ++it) { if (it.key() != "G" && it.key() != "A") { failingChannelKey = it.key(); break; } } info.remappedChannels.push_back( ExrPaintLayerInfo::Remap(failingChannelKey, "G")); QString failingChannelValue = info.channelMap[failingChannelKey]; info.channelMap.remove(failingChannelKey); info.channelMap["G"] = failingChannelValue; } else if (info.channelMap.size() == 3 || info.channelMap.size() == 4) { if (info.channelMap.contains("R") && info.channelMap.contains("G") && info.channelMap.contains("B")) { modelId = RGBAColorModelID.id(); } else if (info.channelMap.contains("X") && info.channelMap.contains("Y") && info.channelMap.contains("Z")) { modelId = XYZAColorModelID.id(); QMap newChannelMap; if (info.channelMap.contains("W")) { newChannelMap["A"] = info.channelMap["W"]; info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("W", "A")); info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("X", "X")); info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Y", "Y")); info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Z", "Z")); } else if (info.channelMap.contains("A")) { newChannelMap["A"] = info.channelMap["A"]; } // The decode function expect R, G, B in the channel map newChannelMap["B"] = info.channelMap["X"]; newChannelMap["G"] = info.channelMap["Y"]; newChannelMap["R"] = info.channelMap["Z"]; info.channelMap = newChannelMap; } else { modelId = RGBAColorModelID.id(); QMap newChannelMap; QMap::iterator it = info.channelMap.begin(); newChannelMap["R"] = it.value(); info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "R")); ++it; newChannelMap["G"] = it.value(); info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "G")); ++it; newChannelMap["B"] = it.value(); info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "B")); if (info.channelMap.size() == 4) { ++it; newChannelMap["A"] = it.value(); info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "A")); } info.channelMap = newChannelMap; } } else { dbgFile << info.name << "has" << info.channelMap.size() << "channels, and we don't know what to do."; } if (!modelId.isEmpty()) { info.colorSpace = kisTypeToColorSpace(modelId, info.imageType); } } // Get colorspace dbgFile << "Image type = " << imageType; const KoColorSpace* colorSpace = kisTypeToColorSpace(RGBAColorModelID.id(), imageType); if (!colorSpace) return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; dbgFile << "Colorspace: " << colorSpace->name(); // Set the colorspace on all groups for (int i = 0; i < groups.size(); ++i) { ExrGroupLayerInfo& info = groups[i]; info.colorSpace = colorSpace; } // Create the image // Make sure the created image is the same size as the displayWindow since // the dataWindow can be cropped in some cases. int displayWidth = displayWindow.max.x - displayWindow.min.x + 1; int displayHeight = displayWindow.max.y - displayWindow.min.y + 1; d->image = new KisImage(d->doc->createUndoStore(), displayWidth, displayHeight, colorSpace, ""); if (!d->image) { return KisImageBuilder_RESULT_FAILURE; } /** * EXR semi-transparent images are expected to be rendered on * black to ensure correctness of the light model */ d->image->setDefaultProjectionColor(KoColor(Qt::black, colorSpace)); // Create group layers for (int i = 0; i < groups.size(); ++i) { ExrGroupLayerInfo& info = groups[i]; Q_ASSERT(info.parent == 0 || info.parent->groupLayer); KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer(); info.groupLayer = new KisGroupLayer(d->image, info.name, OPACITY_OPAQUE_U8); d->image->addNode(info.groupLayer, groupLayerParent); } // Load the layers for (int i = informationObjects.size() - 1; i >= 0; --i) { ExrPaintLayerInfo& info = informationObjects[i]; if (info.colorSpace) { dbgFile << "Decoding " << info.name << " with " << info.channelMap.size() << " channels, and color space " << info.colorSpace->id(); KisPaintLayerSP layer = new KisPaintLayer(d->image, info.name, OPACITY_OPAQUE_U8, info.colorSpace); layer->setCompositeOpId(COMPOSITE_OVER); if (!layer) { return KisImageBuilder_RESULT_FAILURE; } switch (info.channelMap.size()) { case 1: case 2: // Decode the data switch (info.imageType) { case IT_FLOAT16: d->decodeData1(file, info, layer, width, dx, dy, height, Imf::HALF); break; case IT_FLOAT32: d->decodeData1(file, info, layer, width, dx, dy, height, Imf::FLOAT); break; case IT_UNKNOWN: case IT_UNSUPPORTED: qFatal("Impossible error"); } break; case 3: case 4: // Decode the data switch (info.imageType) { case IT_FLOAT16: d->decodeData4(file, info, layer, width, dx, dy, height, Imf::HALF); break; case IT_FLOAT32: d->decodeData4(file, info, layer, width, dx, dy, height, Imf::FLOAT); break; case IT_UNKNOWN: case IT_UNSUPPORTED: qFatal("Impossible error"); } break; default: qFatal("Invalid number of channels: %i", info.channelMap.size()); } // Check if should set the channels if (!info.remappedChannels.isEmpty()) { QList values; Q_FOREACH (const ExrPaintLayerInfo::Remap& remap, info.remappedChannels) { QMap map; map["original"] = KisMetaData::Value(remap.original); map["current"] = KisMetaData::Value(remap.current); values.append(map); } layer->metaData()->addEntry(KisMetaData::Entry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap", values)); } // Add the layer KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer(); d->image->addNode(layer, groupLayerParent); } else { dbgFile << "No decoding " << info.name << " with " << info.channelMap.size() << " channels, and lack of a color space"; } } // Set projectionColor to opaque d->image->setDefaultProjectionColor(KoColor(Qt::transparent, colorSpace)); // After reading the image, notify the user about changed alpha. if (d->alphaWasModified) { QString msg = i18nc("@info", "The image contains pixels with zero alpha channel and non-zero " "color channels. Krita has modified those pixels to have " "at least some alpha. The initial values will not " "be reverted on saving the image back." "

" "This will hardly make any visual difference just keep it in mind."); if (d->showNotifications) { QMessageBox::information(0, i18nc("@title:window", "EXR image has been modified"), msg); } else { warnKrita << "WARNING:" << msg; } } if (!extraLayersInfo.isNull()) { KisExrLayersSorter sorter(extraLayersInfo, d->image); } return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result EXRConverter::buildImage(const QString &filename) { return decode(filename); } KisImageSP EXRConverter::image() { return d->image; } QString EXRConverter::errorMessage() const { return d->errorMessage; } template struct ExrPixel_ { _T_ data[size]; }; class Encoder { public: virtual ~Encoder() {} virtual void prepareFrameBuffer(Imf::FrameBuffer*, int line) = 0; virtual void encodeData(int line) = 0; }; template class EncoderImpl : public Encoder { public: EncoderImpl(Imf::OutputFile* _file, const ExrPaintLayerSaveInfo* _info, int width) : file(_file), info(_info), pixels(width), m_width(width) {} ~EncoderImpl() override {} void prepareFrameBuffer(Imf::FrameBuffer*, int line) override; void encodeData(int line) override; private: typedef ExrPixel_<_T_, size> ExrPixel; Imf::OutputFile* file; const ExrPaintLayerSaveInfo* info; QVector pixels; int m_width; }; template void EncoderImpl<_T_, size, alphaPos>::prepareFrameBuffer(Imf::FrameBuffer* frameBuffer, int line) { int xstart = 0; int ystart = 0; ExrPixel* frameBufferData = (pixels.data()) - xstart - (ystart + line) * m_width; for (int k = 0; k < size; ++k) { frameBuffer->insert(info->channels[k].toUtf8(), Imf::Slice(info->pixelType, (char *) &frameBufferData->data[k], sizeof(ExrPixel) * 1, sizeof(ExrPixel) * m_width)); } } template void EncoderImpl<_T_, size, alphaPos>::encodeData(int line) { ExrPixel *rgba = pixels.data(); KisHLineIteratorSP it = info->layer->paintDevice()->createHLineIteratorNG(0, line, m_width); do { const _T_* dst = reinterpret_cast < const _T_* >(it->oldRawData()); for (int i = 0; i < size; ++i) { rgba->data[i] = dst[i]; } if (alphaPos != -1) { multiplyAlpha<_T_, ExrPixel, size, alphaPos>(rgba); } ++rgba; } while (it->nextPixel()); } Encoder* encoder(Imf::OutputFile& file, const ExrPaintLayerSaveInfo& info, int width) { dbgFile << "Create encoder for" << info.layer->name() << info.channels << info.layer->colorSpace()->channelCount(); switch (info.layer->colorSpace()->channelCount()) { case 1: { if (info.layer->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::HALF); return new EncoderImpl < half, 1, -1 > (&file, &info, width); } else if (info.layer->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::FLOAT); return new EncoderImpl < float, 1, -1 > (&file, &info, width); } break; } case 2: { if (info.layer->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::HALF); return new EncoderImpl(&file, &info, width); } else if (info.layer->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::FLOAT); return new EncoderImpl(&file, &info, width); } break; } case 4: { if (info.layer->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::HALF); return new EncoderImpl(&file, &info, width); } else if (info.layer->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::FLOAT); return new EncoderImpl(&file, &info, width); } break; } default: qFatal("Impossible error"); } return 0; } void encodeData(Imf::OutputFile& file, const QList& informationObjects, int width, int height) { QList encoders; Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) { encoders.push_back(encoder(file, info, width)); } for (int y = 0; y < height; ++y) { Imf::FrameBuffer frameBuffer; Q_FOREACH (Encoder* encoder, encoders) { encoder->prepareFrameBuffer(&frameBuffer, y); } file.setFrameBuffer(frameBuffer); Q_FOREACH (Encoder* encoder, encoders) { encoder->encodeData(y); } file.writePixels(1); } qDeleteAll(encoders); } KisImageBuilder_Result EXRConverter::buildFile(const QString &filename, KisPaintLayerSP layer) { if (!layer) return KisImageBuilder_RESULT_INVALID_ARG; KisImageSP image = layer->image(); if (!image) return KisImageBuilder_RESULT_EMPTY; // Make the header qint32 height = image->height(); qint32 width = image->width(); Imf::Header header(width, height); Imf::PixelType pixelType = Imf::NUM_PIXELTYPES; if (layer->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { pixelType = Imf::HALF; } else if(layer->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { pixelType = Imf::FLOAT; } else { const KoColorSpace *cs = 0; if (layer->colorSpace()->colorModelId() == GrayAColorModelID) { cs = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Float16BitsColorDepthID.id()); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float16BitsColorDepthID.id()); } image->convertImageColorSpace(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); pixelType = Imf::HALF; } header.channels().insert("R", Imf::Channel(pixelType)); header.channels().insert("G", Imf::Channel(pixelType)); header.channels().insert("B", Imf::Channel(pixelType)); header.channels().insert("A", Imf::Channel(pixelType)); ExrPaintLayerSaveInfo info; info.layer = layer; info.channels.push_back("R"); info.channels.push_back("G"); info.channels.push_back("B"); info.channels.push_back("A"); info.pixelType = pixelType; // Open file for writing Imf::OutputFile file(QFile::encodeName(filename), header); QList informationObjects; informationObjects.push_back(info); encodeData(file, informationObjects, width, height); return KisImageBuilder_RESULT_OK; } QString remap(const QMap& current2original, const QString& current) { if (current2original.contains(current)) { return current2original[current]; } return current; } void EXRConverter::Private::makeLayerNamesUnique(QList& informationObjects) { typedef QMultiMap::iterator> NamesMap; NamesMap namesMap; { QList::iterator it = informationObjects.begin(); QList::iterator end = informationObjects.end(); for (; it != end; ++it) { namesMap.insert(it->name, it); } } Q_FOREACH (const QString &key, namesMap.keys()) { if (namesMap.count(key) > 1) { KIS_ASSERT_RECOVER(key.endsWith(".")) { continue; } QString strippedName = key.left(key.size() - 1); // trim the ending dot int nameCounter = 0; NamesMap::iterator it = namesMap.find(key); NamesMap::iterator end = namesMap.end(); for (; it != end; ++it) { QString newName = QString("%1_%2.") .arg(strippedName) .arg(nameCounter++); it.value()->name = newName; QList::iterator channelsIt = it.value()->channels.begin(); QList::iterator channelsEnd = it.value()->channels.end(); for (; channelsIt != channelsEnd; ++channelsIt) { channelsIt->replace(key, newName); } } } } } void EXRConverter::Private::recBuildPaintLayerSaveInfo(QList& informationObjects, const QString& name, KisGroupLayerSP parent) { QSet layersNotSaved; for (uint i = 0; i < parent->childCount(); ++i) { KisNodeSP node = parent->at(i); if (KisPaintLayerSP paintLayer = dynamic_cast(node.data())) { QMap current2original; if (paintLayer->metaData()->containsEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap")) { const KisMetaData::Entry& entry = paintLayer->metaData()->getEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap"); QList< KisMetaData::Value> values = entry.value().asArray(); Q_FOREACH (const KisMetaData::Value& value, values) { QMap map = value.asStructure(); if (map.contains("original") && map.contains("current")) { current2original[map["current"].toString()] = map["original"].toString(); } } } ExrPaintLayerSaveInfo info; info.name = name + paintLayer->name() + '.'; info.layer = paintLayer; if (info.name == QString(HDR_LAYER) + ".") { info.channels.push_back("R"); info.channels.push_back("G"); info.channels.push_back("B"); info.channels.push_back("A"); } else { if (paintLayer->colorSpace()->colorModelId() == RGBAColorModelID) { info.channels.push_back(info.name + remap(current2original, "R")); info.channels.push_back(info.name + remap(current2original, "G")); info.channels.push_back(info.name + remap(current2original, "B")); info.channels.push_back(info.name + remap(current2original, "A")); } else if (paintLayer->colorSpace()->colorModelId() == GrayAColorModelID) { info.channels.push_back(info.name + remap(current2original, "G")); info.channels.push_back(info.name + remap(current2original, "A")); } else if (paintLayer->colorSpace()->colorModelId() == GrayColorModelID) { info.channels.push_back(info.name + remap(current2original, "G")); } else if (paintLayer->colorSpace()->colorModelId() == XYZAColorModelID) { info.channels.push_back(info.name + remap(current2original, "X")); info.channels.push_back(info.name + remap(current2original, "Y")); info.channels.push_back(info.name + remap(current2original, "Z")); info.channels.push_back(info.name + remap(current2original, "A")); } } if (paintLayer->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { info.pixelType = Imf::HALF; } else if (paintLayer->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { info.pixelType = Imf::FLOAT; } else { info.pixelType = Imf::NUM_PIXELTYPES; } if (info.pixelType < Imf::NUM_PIXELTYPES) { dbgFile << "Going to save layer" << info.name; informationObjects.push_back(info); } else { warnFile << "Will not save layer" << info.name; layersNotSaved << node; } } else if (KisGroupLayerSP groupLayer = dynamic_cast(node.data())) { recBuildPaintLayerSaveInfo(informationObjects, name + groupLayer->name() + '.', groupLayer); } else { /** * The EXR can store paint and group layers only. The rest will * go to /dev/null :( */ layersNotSaved.insert(node); } } if (!layersNotSaved.isEmpty()) { reportLayersNotSaved(layersNotSaved); } } void EXRConverter::Private::reportLayersNotSaved(const QSet &layersNotSaved) { QString layersList; QTextStream textStream(&layersList); textStream.setCodec("UTF-8"); Q_FOREACH (KisNodeSP node, layersNotSaved) { textStream << "
  • " << i18nc("@item:unsupported-node-message", "%1 (type: \"%2\")", node->name(), node->metaObject()->className()) << "
  • "; } QString msg = i18nc("@info", "

    The following layers have a type that is not supported by EXR format:

    " "
      %1

    " "

    these layers have not been saved to the final EXR file

    ", layersList); errorMessage = msg; } QString EXRConverter::Private::fetchExtraLayersInfo(QList& informationObjects) { KIS_ASSERT_RECOVER_NOOP(!informationObjects.isEmpty()); if (informationObjects.size() == 1 && informationObjects[0].name == QString(HDR_LAYER) + ".") { return QString(); } QDomDocument doc("krita-extra-layers-info"); doc.appendChild(doc.createElement("root")); QDomElement rootElement = doc.documentElement(); for (int i = 0; i < informationObjects.size(); i++) { ExrPaintLayerSaveInfo &info = informationObjects[i]; quint32 unused; KisSaveXmlVisitor visitor(doc, rootElement, unused, QString(), false); QDomElement el = visitor.savePaintLayerAttributes(info.layer.data(), doc); // cut the ending '.' QString strippedName = info.name.left(info.name.size() - 1); el.setAttribute(EXR_NAME, strippedName); rootElement.appendChild(el); } return doc.toString(); } KisImageBuilder_Result EXRConverter::buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten) { if (!layer) return KisImageBuilder_RESULT_INVALID_ARG; KisImageSP image = layer->image(); if (!image) return KisImageBuilder_RESULT_EMPTY; qint32 height = image->height(); qint32 width = image->width(); Imf::Header header(width, height); if (image->colorSpace()->colorDepthId() != Float16BitsColorDepthID && image->colorSpace()->colorDepthId() != Float32BitsColorDepthID) { const KoColorSpace *cs = 0; if (layer->colorSpace()->colorModelId() == GrayAColorModelID) { cs = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Float16BitsColorDepthID.id()); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float16BitsColorDepthID.id()); } image->convertImageColorSpace(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } if (flatten) { image->waitForDone(); // This is to make sure we have a full image to project. KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd); return buildFile(filename, l); } else { QList informationObjects; d->recBuildPaintLayerSaveInfo(informationObjects, "", layer); if(informationObjects.isEmpty()) { return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } d->makeLayerNamesUnique(informationObjects); QByteArray extraLayersInfo = d->fetchExtraLayersInfo(informationObjects).toUtf8(); if (!extraLayersInfo.isNull()) { header.insert(EXR_KRITA_LAYERS, Imf::StringAttribute(extraLayersInfo.constData())); } dbgFile << informationObjects.size() << " layers to save"; Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) { if (info.pixelType < Imf::NUM_PIXELTYPES) { Q_FOREACH (const QString& channel, info.channels) { dbgFile << channel << " " << info.pixelType; header.channels().insert(channel.toUtf8().data(), Imf::Channel(info.pixelType)); } } } // Open file for writing Imf::OutputFile file(QFile::encodeName(filename), header); encodeData(file, informationObjects, width, height); return KisImageBuilder_RESULT_OK; } } void EXRConverter::cancel() { warnKrita << "WARNING: Cancelling of an EXR loading is not supported!"; } diff --git a/plugins/impex/heif/HeifExport.cpp b/plugins/impex/heif/HeifExport.cpp index dab25b1d51..6884c91a7d 100644 --- a/plugins/impex/heif/HeifExport.cpp +++ b/plugins/impex/heif/HeifExport.cpp @@ -1,281 +1,281 @@ /* * Copyright (c) 2018 Dirk Farin * * 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 "HeifExport.h" #include "HeifError.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 "kis_iterator_ng.h" #include "libheif/heif_cxx.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_heif_export.json", registerPlugin();) HeifExport::HeifExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } HeifExport::~HeifExport() { } KisPropertiesConfigurationSP HeifExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("quality", 50); cfg->setProperty("lossless", true); return cfg; } KisConfigWidget *HeifExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisWdgOptionsHeif(parent); } class Writer_QIODevice : public heif::Context::Writer { public: Writer_QIODevice(QIODevice* io) : m_io(io) { } heif_error write(const void* data, size_t size) override { qint64 n = m_io->write((const char*)data,size); if (n != (qint64)size) { QString error = m_io->errorString(); heif_error err = { heif_error_Encoding_error, heif_suberror_Cannot_write_output_data, "Could not write output data" }; return err; } struct heif_error heif_error_ok = { heif_error_Ok, heif_suberror_Unspecified, "Success" }; return heif_error_ok; } private: QIODevice* m_io; }; KisImportExportFilter::ConversionStatus HeifExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { KisImageSP image = document->savingImage(); const KoColorSpace *cs = image->colorSpace(); // Convert to 8 bits rgba on saving if (cs->colorModelId() != RGBAColorModelID || cs->colorDepthId() != Integer8BitsColorDepthID) { cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id()); image->convertImageColorSpace(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } int quality = configuration->getInt("quality", 50); bool lossless = configuration->getBool("lossless", false); bool has_alpha = configuration->getBool(KisImportExportFilter::ImageContainsTransparencyTag, false); // If we want to add information from the document to the metadata, // we should do that here. try { // --- use standard HEVC encoder heif::Encoder encoder(heif_compression_HEVC); encoder.set_lossy_quality(quality); encoder.set_lossless(lossless); // --- convert KisImage to HEIF image --- int width = image->width(); int height = image->height(); heif::Context ctx; heif::Image img; img.create(width,height, heif_colorspace_RGB, heif_chroma_444); img.add_plane(heif_channel_R, width,height, 8); img.add_plane(heif_channel_G, width,height, 8); img.add_plane(heif_channel_B, width,height, 8); uint8_t* ptrR {0}; uint8_t* ptrG {0}; uint8_t* ptrB {0}; uint8_t* ptrA {0}; int strideR,strideG,strideB,strideA; ptrR = img.get_plane(heif_channel_R, &strideR); ptrG = img.get_plane(heif_channel_G, &strideG); ptrB = img.get_plane(heif_channel_B, &strideB); if (has_alpha) { img.add_plane(heif_channel_Alpha, width,height, 8); ptrA = img.get_plane(heif_channel_Alpha, &strideA); } KisPaintDeviceSP pd = image->projection(); for (int y=0; ycreateHLineIteratorNG(0, y, width); for (int x=0; x::red(it->rawData()); ptrG[y*strideG+x] = KoBgrTraits::green(it->rawData()); ptrB[y*strideB+x] = KoBgrTraits::blue(it->rawData()); if (has_alpha) { ptrA[y*strideA+x] = cs->opacityU8(it->rawData()); } it->nextPixel(); } } // --- encode and write image heif::ImageHandle handle = ctx.encode_image(img, encoder); // --- add Exif / XMP metadata KisExifInfoVisitor exivInfoVisitor; exivInfoVisitor.visit(image->rootLayer().data()); QScopedPointer metaDataStore; if (exivInfoVisitor.metaDataCount() == 1) { metaDataStore.reset(new KisMetaData::Store(*exivInfoVisitor.exifInfo())); } else { metaDataStore.reset(new KisMetaData::Store()); } if (!metaDataStore->empty()) { { KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); QBuffer buffer; exifIO->saveTo(metaDataStore.data(), &buffer, KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else? QByteArray data = buffer.data(); // Write the data to the file ctx.add_exif_metadata(handle, data.constData(), data.size()); } { KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); QBuffer buffer; xmpIO->saveTo(metaDataStore.data(), &buffer, KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else? QByteArray data = buffer.data(); // Write the data to the file ctx.add_XMP_metadata(handle, data.constData(), data.size()); } } // --- write HEIF file Writer_QIODevice writer(io); ctx.write(writer); } catch (heif::Error err) { return setHeifError(document, err); } return KisImportExportFilter::OK; } void HeifExport::initializeCapabilities() { // This checks before saving for what the file format supports: anything that is supported needs to be mentioned here QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) /*<< QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID)*/ ; addSupportedColorModels(supportedColorModels, "HEIF"); } void KisWdgOptionsHeif::setConfiguration(const KisPropertiesConfigurationSP cfg) { chkLossless->setChecked(cfg->getBool("lossless", true)); sliderQuality->setValue(cfg->getInt("quality", 50)); } KisPropertiesConfigurationSP KisWdgOptionsHeif::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("lossless", chkLossless->isChecked()); cfg->setProperty("quality", sliderQuality->value()); return cfg; } void KisWdgOptionsHeif::toggleQualitySlider(bool toggle) { // Disable the quality slider if lossless is true sliderQuality->setEnabled(!toggle); } #include diff --git a/plugins/impex/heif/HeifImport.cpp b/plugins/impex/heif/HeifImport.cpp index 7875a4f33e..ec9675feba 100644 --- a/plugins/impex/heif/HeifImport.cpp +++ b/plugins/impex/heif/HeifImport.cpp @@ -1,189 +1,189 @@ /* * Copyright (c) 2018 Dirk Farin * * 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 "HeifImport.h" #include "HeifError.h" #include #include #include #include #include #include #include #include #include #include #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include "kis_iterator_ng.h" #include "libheif/heif_cxx.h" K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_heif_import.json", registerPlugin();) HeifImport::HeifImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } HeifImport::~HeifImport() { } class Reader_QIODevice : public heif::Context::Reader { public: Reader_QIODevice(QIODevice* device) : m_device(device) { m_total_length=m_device->bytesAvailable(); } int64_t get_position() const { return m_device->pos(); } int read(void* data, size_t size) { return m_device->read((char*)data,size) != size; } int seek(int64_t position) { return !m_device->seek(position); } heif_reader_grow_status wait_for_file_size(int64_t target_size) { return (target_size > m_total_length) ? heif_reader_grow_status_size_beyond_eof : heif_reader_grow_status_size_reached; } private: QIODevice* m_device; int64_t m_total_length; }; KisImportExportFilter::ConversionStatus HeifImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { // Wrap input stream into heif Reader object Reader_QIODevice reader(io); try { heif::Context ctx; ctx.read_from_reader(reader); // decode primary image heif::ImageHandle handle = ctx.get_primary_image_handle(); heif::Image heifimage = handle.decode_image(heif_colorspace_RGB, heif_chroma_444); int width =handle.get_width(); int height=handle.get_height(); bool hasAlpha = handle.has_alpha_channel(); // convert HEIF image to Krita KisDocument int strideR, strideG, strideB, strideA; const uint8_t* imgR = heifimage.get_plane(heif_channel_R, &strideR); const uint8_t* imgG = heifimage.get_plane(heif_channel_G, &strideG); const uint8_t* imgB = heifimage.get_plane(heif_channel_B, &strideB); const uint8_t* imgA = heifimage.get_plane(heif_channel_Alpha, &strideA); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(document->createUndoStore(), width, height, colorSpace, "HEIF image"); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); for (int y=0;ypaintDevice()->createHLineIteratorNG(0, y, width); for (int x=0;x::setRed(it->rawData(), imgR[y*strideR+x]); KoBgrTraits::setGreen(it->rawData(), imgG[y*strideG+x]); KoBgrTraits::setBlue(it->rawData(), imgB[y*strideB+x]); if (hasAlpha) { colorSpace->setOpacity(it->rawData(), quint8(imgA[y*strideA+x]), 1); } else { colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); } it->nextPixel(); } } image->addNode(layer.data(), image->rootLayer().data()); // --- Iterate through all metadata blocks and extract Exif and XMP metadata --- std::vector metadata_IDs = handle.get_list_of_metadata_block_IDs(); for (heif_item_id id : metadata_IDs) { if (handle.get_metadata_type(id) == "Exif") { // Read exif information std::vector exif_data = handle.get_metadata(id); if (exif_data.size()>4) { uint32_t skip = ((exif_data[0]<<24) | (exif_data[1]<<16) | (exif_data[2]<<8) | exif_data[3]) + 4; if (exif_data.size()>skip) { KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); // Copy the exif data into the byte array QByteArray ba; ba.append((char*)(exif_data.data()+skip), exif_data.size()-skip); QBuffer buf(&ba); exifIO->loadFrom(layer->metaData(), &buf); } } } if (handle.get_metadata_type(id) == "mime" && handle.get_metadata_content_type(id) == "application/rdf+xml") { // Read XMP information std::vector xmp_data = handle.get_metadata(id); KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); // Copy the xmp data into the byte array QByteArray ba; ba.append((char*)(xmp_data.data()), xmp_data.size()); QBuffer buf(&ba); xmpIO->loadFrom(layer->metaData(), &buf); } } document->setCurrentImage(image); return KisImportExportFilter::OK; } catch (heif::Error err) { return setHeifError(document, err); } } #include diff --git a/plugins/impex/jpeg/kis_jpeg_converter.cc b/plugins/impex/jpeg/kis_jpeg_converter.cc index 0a759730d4..b2c7484694 100644 --- a/plugins/impex/jpeg/kis_jpeg_converter.cc +++ b/plugins/impex/jpeg/kis_jpeg_converter.cc @@ -1,732 +1,732 @@ /* * Copyright (c) 2005 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_jpeg_converter.h" #include #include #include #ifdef HAVE_LCMS2 # include #else # include #endif extern "C" { #include } #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorModelStandardIds.h" #include #include #include #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include #include #include "kis_iterator_ng.h" #define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ #define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ #define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ #define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) const char photoshopMarker[] = "Photoshop 3.0\0"; //const char photoshopBimId_[] = "8BIM"; const uint16_t photoshopIptc = 0x0404; const char xmpMarker[] = "http://ns.adobe.com/xap/1.0/\0"; const QByteArray photoshopIptc_((char*)&photoshopIptc, 2); namespace { void jpegErrorExit ( j_common_ptr cinfo ) { char jpegLastErrorMsg[JMSG_LENGTH_MAX]; /* Create the message */ ( *( cinfo->err->format_message ) ) ( cinfo, jpegLastErrorMsg ); /* Jump to the setjmp point */ throw std::runtime_error( jpegLastErrorMsg ); // or your preferred exception ... } J_COLOR_SPACE getColorTypeforColorSpace(const KoColorSpace * cs) { if (KoID(cs->id()) == KoID("GRAYA") || cs->id() == "GRAYAU16" || cs->id() == "GRAYA16") { return JCS_GRAYSCALE; } if (KoID(cs->id()) == KoID("RGBA") || KoID(cs->id()) == KoID("RGBA16")) { return JCS_RGB; } if (KoID(cs->id()) == KoID("CMYK") || KoID(cs->id()) == KoID("CMYKAU16")) { return JCS_CMYK; } return JCS_UNKNOWN; } QString getColorSpaceModelForColorType(J_COLOR_SPACE color_type) { dbgFile << "color_type =" << color_type; if (color_type == JCS_GRAYSCALE) { return GrayAColorModelID.id(); } else if (color_type == JCS_RGB) { return RGBAColorModelID.id(); } else if (color_type == JCS_CMYK) { return CMYKAColorModelID.id(); } return ""; } } struct KisJPEGConverter::Private { Private(KisDocument *doc, bool batchMode) : doc(doc), stop(false), batchMode(batchMode) {} KisImageSP image; KisDocument *doc; bool stop; bool batchMode; }; KisJPEGConverter::KisJPEGConverter(KisDocument *doc, bool batchMode) : m_d(new Private(doc, batchMode)) { } KisJPEGConverter::~KisJPEGConverter() { } KisImageBuilder_Result KisJPEGConverter::decode(QIODevice *io) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = jpegErrorExit; try { jpeg_create_decompress(&cinfo); KisJPEGSource::setSource(&cinfo, io); jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF); /* Save APP0..APP15 markers */ for (int m = 0; m < 16; m++) jpeg_save_markers(&cinfo, JPEG_APP0 + m, 0xFFFF); // setup_read_icc_profile(&cinfo); // read header jpeg_read_header(&cinfo, (boolean)true); // start reading jpeg_start_decompress(&cinfo); // Get the colorspace QString modelId = getColorSpaceModelForColorType(cinfo.out_color_space); if (modelId.isEmpty()) { dbgFile << "unsupported colorspace :" << cinfo.out_color_space; jpeg_destroy_decompress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } uchar* profile_data; uint profile_len; const KoColorProfile* profile = 0; QByteArray profile_rawdata; if (read_icc_profile(&cinfo, &profile_data, &profile_len)) { profile_rawdata.resize(profile_len); memcpy(profile_rawdata.data(), profile_data, profile_len); cmsHPROFILE hProfile = cmsOpenProfileFromMem(profile_data, profile_len); if (hProfile != (cmsHPROFILE) 0) { profile = KoColorSpaceRegistry::instance()->createColorProfile(modelId, Integer8BitsColorDepthID.id(), profile_rawdata); Q_CHECK_PTR(profile); dbgFile <<"profile name:" << profile->name() <<" product information:" << profile->info(); 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 } } } const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(modelId, Integer8BitsColorDepthID.id()); // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << modelId; profile = 0; } // Retrieve a pointer to the colorspace const KoColorSpace* cs; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile:" << profile -> name() << ""; cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile); } else cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), ""); if (cs == 0) { dbgFile << "unknown colorspace"; jpeg_destroy_decompress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // TODO fixit // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { transform = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Apparently an invalid transform was created from the profile. See bug https://bugs.kde.org/show_bug.cgi?id=255451. // After 2.3: warn the user! if (transform && !transform->isValid()) { delete transform; transform = 0; } // Creating the KisImageSP if (!m_d->image) { m_d->image = new KisImage(m_d->doc->createUndoStore(), cinfo.image_width, cinfo.image_height, cs, "built image"); Q_CHECK_PTR(m_d->image); } // Set resolution double xres = 72, yres = 72; if (cinfo.density_unit == 1) { xres = cinfo.X_density; yres = cinfo.Y_density; } else if (cinfo.density_unit == 2) { xres = cinfo.X_density * 2.54; yres = cinfo.Y_density * 2.54; } if (xres < 72) { xres = 72; } if (yres < 72) { yres = 72; } m_d->image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points // Create layer KisPaintLayerSP layer = KisPaintLayerSP(new KisPaintLayer(m_d->image.data(), m_d->image -> nextLayerName(), quint8_MAX)); // Read data JSAMPROW row_pointer = new JSAMPLE[cinfo.image_width*cinfo.num_components]; for (; cinfo.output_scanline < cinfo.image_height;) { KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, cinfo.output_scanline, cinfo.image_width); jpeg_read_scanlines(&cinfo, &row_pointer, 1); quint8 *src = row_pointer; switch (cinfo.out_color_space) { case JCS_GRAYSCALE: do { quint8 *d = it->rawData(); d[0] = *(src++); if (transform) transform->transform(d, d, 1); d[1] = quint8_MAX; } while (it->nextPixel()); break; case JCS_RGB: do { quint8 *d = it->rawData(); d[2] = *(src++); d[1] = *(src++); d[0] = *(src++); if (transform) transform->transform(d, d, 1); d[3] = quint8_MAX; } while (it->nextPixel()); break; case JCS_CMYK: do { quint8 *d = it->rawData(); d[0] = quint8_MAX - *(src++); d[1] = quint8_MAX - *(src++); d[2] = quint8_MAX - *(src++); d[3] = quint8_MAX - *(src++); if (transform) transform->transform(d, d, 1); d[4] = quint8_MAX; } while (it->nextPixel()); break; default: return KisImageBuilder_RESULT_UNSUPPORTED; } } m_d->image->addNode(KisNodeSP(layer.data()), m_d->image->rootLayer().data()); // Read exif information dbgFile << "Looking for exif information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 14) { continue; /* Exif data is in an APP1 marker of at least 14 octets */ } if (GETJOCTET(marker->data[0]) != (JOCTET) 0x45 || GETJOCTET(marker->data[1]) != (JOCTET) 0x78 || GETJOCTET(marker->data[2]) != (JOCTET) 0x69 || GETJOCTET(marker->data[3]) != (JOCTET) 0x66 || GETJOCTET(marker->data[4]) != (JOCTET) 0x00 || GETJOCTET(marker->data[5]) != (JOCTET) 0x00) continue; /* no Exif header */ dbgFile << "Found exif information of length :" << marker->data_length; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QByteArray byteArray((const char*)marker->data + 6, marker->data_length - 6); QBuffer buf(&byteArray); exifIO->loadFrom(layer->metaData(), &buf); // Interpret orientation tag if (layer->metaData()->containsEntry("http://ns.adobe.com/tiff/1.0/", "Orientation")) { KisMetaData::Entry& entry = layer->metaData()->getEntry("http://ns.adobe.com/tiff/1.0/", "Orientation"); if (entry.value().type() == KisMetaData::Value::Variant) { switch (entry.value().asVariant().toInt()) { case 2: KisTransformWorker::mirrorY(layer->paintDevice()); break; case 3: image()->rotateImage(M_PI); break; case 4: KisTransformWorker::mirrorX(layer->paintDevice()); break; case 5: image()->rotateImage(M_PI / 2); KisTransformWorker::mirrorY(layer->paintDevice()); break; case 6: image()->rotateImage(M_PI / 2); break; case 7: image()->rotateImage(M_PI / 2); KisTransformWorker::mirrorX(layer->paintDevice()); break; case 8: image()->rotateImage(-M_PI / 2 + M_PI*2); break; default: break; } } entry.value().setVariant(1); } break; } dbgFile << "Looking for IPTC information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 13) || marker->data_length < 14) { continue; /* IPTC data is in an APP13 marker of at least 16 octets */ } if (memcmp(marker->data, photoshopMarker, 14) != 0) { for (int i = 0; i < 14; i++) { dbgFile << (int)(*(marker->data + i)) << "" << (int)(photoshopMarker[i]); } dbgFile << "No photoshop marker"; continue; /* No IPTC Header */ } dbgFile << "Found Photoshop information of length :" << marker->data_length; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); const Exiv2::byte *record = 0; uint32_t sizeIptc = 0; uint32_t sizeHdr = 0; // Find actual Iptc data within the APP13 segment if (!Exiv2::Photoshop::locateIptcIrb((Exiv2::byte*)(marker->data + 14), marker->data_length - 14, &record, &sizeHdr, &sizeIptc)) { if (sizeIptc) { // Decode the IPTC data QByteArray byteArray((const char*)(record + sizeHdr), sizeIptc); QBuffer buf(&byteArray); iptcIO->loadFrom(layer->metaData(), &buf); } else { dbgFile << "IPTC Not found in Photoshop marker"; } } break; } dbgFile << "Looking for XMP information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 31) { continue; /* XMP data is in an APP1 marker of at least 31 octets */ } if (memcmp(marker->data, xmpMarker, 29) != 0) { dbgFile << "Not XMP marker"; continue; /* No xmp Header */ } dbgFile << "Found XMP Marker of length " << marker->data_length; QByteArray byteArray((const char*)marker->data + 29, marker->data_length - 29); KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); xmpIO->loadFrom(layer->metaData(), new QBuffer(&byteArray)); break; } // Dump loaded metadata layer->metaData()->debugDump(); // Check whether the metadata has resolution info, too... if (cinfo.density_unit == 0 && layer->metaData()->containsEntry("tiff:XResolution") && layer->metaData()->containsEntry("tiff:YResolution")) { double xres = layer->metaData()->getEntry("tiff:XResolution").value().asDouble(); double yres = layer->metaData()->getEntry("tiff:YResolution").value().asDouble(); if (xres != 0 && yres != 0) { m_d->image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points } } // Finish decompression jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); delete [] row_pointer; return KisImageBuilder_RESULT_OK; } catch( std::runtime_error &e) { jpeg_destroy_decompress(&cinfo); return KisImageBuilder_RESULT_FAILURE; } } KisImageBuilder_Result KisJPEGConverter::buildImage(QIODevice *io) { return decode(io); } KisImageSP KisJPEGConverter::image() { return m_d->image; } KisImageBuilder_Result KisJPEGConverter::buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData) { if (!layer) return KisImageBuilder_RESULT_INVALID_ARG; KisImageSP image = KisImageSP(layer->image()); if (!image) return KisImageBuilder_RESULT_EMPTY; const KoColorSpace * cs = layer->colorSpace(); J_COLOR_SPACE color_type = getColorTypeforColorSpace(cs); if (color_type == JCS_UNKNOWN) { KUndo2Command *tmp = layer->paintDevice()->convertTo(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); delete tmp; cs = KoColorSpaceRegistry::instance()->rgb8(); color_type = JCS_RGB; } if (options.forceSRGB) { const KoColorSpace* dst = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), layer->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)"); KUndo2Command *tmp = layer->paintDevice()->convertTo(dst); delete tmp; cs = dst; color_type = JCS_RGB; } uint height = image->height(); uint width = image->width(); // Initialize structure struct jpeg_compress_struct cinfo; // Initialize error output struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); // Initialize output stream KisJPEGDestination::setDestination(&cinfo, io); cinfo.image_width = width; // image width and height, in pixels cinfo.image_height = height; cinfo.input_components = cs->colorChannelCount(); // number of color channels per pixel */ cinfo.in_color_space = color_type; // colorspace of input image // Set default compression parameters jpeg_set_defaults(&cinfo); // Customize them jpeg_set_quality(&cinfo, options.quality, (boolean)options.baseLineJPEG); if (options.progressive) { jpeg_simple_progression(&cinfo); } // Optimize ? cinfo.optimize_coding = (boolean)options.optimize; // Smoothing cinfo.smoothing_factor = (boolean)options.smooth; // Subsampling switch (options.subsampling) { default: case 0: { cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 1: { cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 2: { cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 3: { cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; } // Save resolution cinfo.X_density = INCH_TO_POINT(image->xRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points cinfo.Y_density = INCH_TO_POINT(image->yRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points cinfo.density_unit = 1; cinfo.write_JFIF_header = (boolean)true; // Start compression jpeg_start_compress(&cinfo, (boolean)true); // Save exif and iptc information if any available if (metaData && !metaData->empty()) { metaData->applyFilters(options.filters); // Save EXIF 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); dbgFile << "Exif information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 1, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "EXIF information could not be saved."; // TODO: warn the user ? } } // 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(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 13, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "IPTC information could not be saved."; // TODO: warn the user ? } } // 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::JpegHeader); dbgFile << "XMP information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 14, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "XMP information could not be saved."; // TODO: warn the user ? } } } KisPaintDeviceSP dev = new KisPaintDevice(layer->colorSpace()); KoColor c(options.transparencyFillColor, layer->colorSpace()); dev->fill(QRect(0, 0, width, height), c); KisPainter gc(dev); gc.bitBlt(QPoint(0, 0), layer->paintDevice(), QRect(0, 0, width, height)); gc.end(); if (options.saveProfile) { const KoColorProfile* colorProfile = layer->colorSpace()->profile(); QByteArray colorProfileData = colorProfile->rawData(); write_icc_profile(& cinfo, (uchar*) colorProfileData.data(), colorProfileData.size()); } // Write data information JSAMPROW row_pointer = new JSAMPLE[width*cinfo.input_components]; int color_nb_bits = 8 * layer->paintDevice()->pixelSize() / layer->paintDevice()->channelCount(); for (; cinfo.next_scanline < height;) { KisHLineConstIteratorSP it = dev->createHLineConstIteratorNG(0, cinfo.next_scanline, width); quint8 *dst = row_pointer; switch (color_type) { case JCS_GRAYSCALE: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = cs->scaleToU8(d, 0);//d[0] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = d[0]; } while (it->nextPixel()); } break; case JCS_RGB: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = cs->scaleToU8(d, 2); //d[2] / quint8_MAX; *(dst++) = cs->scaleToU8(d, 1); //d[1] / quint8_MAX; *(dst++) = cs->scaleToU8(d, 0); //d[0] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; } while (it->nextPixel()); } break; case JCS_CMYK: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = quint8_MAX - cs->scaleToU8(d, 0);//quint8_MAX - d[0] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 1);//quint8_MAX - d[1] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 2);//quint8_MAX - d[2] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 3);//quint8_MAX - d[3] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = quint8_MAX - d[0]; *(dst++) = quint8_MAX - d[1]; *(dst++) = quint8_MAX - d[2]; *(dst++) = quint8_MAX - d[3]; } while (it->nextPixel()); } break; default: delete [] row_pointer; jpeg_destroy_compress(&cinfo); return KisImageBuilder_RESULT_UNSUPPORTED; } jpeg_write_scanlines(&cinfo, &row_pointer, 1); } // Writing is over jpeg_finish_compress(&cinfo); delete [] row_pointer; // Free memory jpeg_destroy_compress(&cinfo); return KisImageBuilder_RESULT_OK; } void KisJPEGConverter::cancel() { m_d->stop = true; } diff --git a/plugins/impex/jpeg/kis_jpeg_export.cc b/plugins/impex/jpeg/kis_jpeg_export.cc index 5980e3e2ad..eb2a7526cd 100644 --- a/plugins/impex/jpeg/kis_jpeg_export.cc +++ b/plugins/impex/jpeg/kis_jpeg_export.cc @@ -1,296 +1,296 @@ /* * Copyright (c) 2005 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_jpeg_export.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 "kis_jpeg_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(KisJPEGExportFactory, "krita_jpeg_export.json", registerPlugin();) KisJPEGExport::KisJPEGExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisJPEGExport::~KisJPEGExport() { } KisImportExportFilter::ConversionStatus KisJPEGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { KisImageSP image = document->savingImage(); Q_CHECK_PTR(image); // An extra option to pass to the config widget to set the state correctly, this isn't saved const KoColorSpace* cs = image->projection()->colorSpace(); bool sRGB = cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive); configuration->setProperty("is_sRGB", sRGB); KisJPEGOptions options; options.progressive = configuration->getBool("progressive", false); options.quality = configuration->getInt("quality", 80); options.forceSRGB = configuration->getBool("forceSRGB", false); options.saveProfile = configuration->getBool("saveProfile", true); options.optimize = configuration->getBool("optimize", true); options.smooth = configuration->getInt("smoothing", 0); options.baseLineJPEG = configuration->getBool("baseline", true); options.subsampling = configuration->getInt("subsampling", 0); options.exif = configuration->getBool("exif", true); options.iptc = configuration->getBool("iptc", true); options.xmp = configuration->getBool("xmp", true); KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromQColor(Qt::white); options.transparencyFillColor = configuration->getColor("transparencyFillcolor", c).toQColor(); KisMetaData::FilterRegistryModel m; m.setEnabledFilters(configuration->getString("filters").split(",")); options.filters = m.enabledFilters(); options.storeAuthor = configuration->getBool("storeAuthor", false); options.storeDocumentMetaData = configuration->getBool("storeMetaData", false); KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); KisJPEGConverter kpc(document, batchMode()); KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd); KisExifInfoVisitor exivInfoVisitor; exivInfoVisitor.visit(image->rootLayer().data()); QScopedPointer metaDataStore; if (exivInfoVisitor.metaDataCount() == 1) { metaDataStore.reset(new KisMetaData::Store(*exivInfoVisitor.exifInfo())); } else { metaDataStore.reset(new KisMetaData::Store()); } //add extra meta-data here const KisMetaData::Schema* dcSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri); Q_ASSERT(dcSchema); if (options.storeDocumentMetaData) { QString title = document->documentInfo()->aboutInfo("title"); if (!title.isEmpty()) { if (metaDataStore->containsEntry("title")) { metaDataStore->removeEntry("title"); } metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "title", KisMetaData::Value(QVariant(title)))); } QString description = document->documentInfo()->aboutInfo("subject"); if (description.isEmpty()) { description = document->documentInfo()->aboutInfo("abstract"); } if (!description.isEmpty()) { QString keywords = document->documentInfo()->aboutInfo("keyword"); if (!keywords.isEmpty()) { description = description + " keywords: " + keywords; } if (metaDataStore->containsEntry("description")) { metaDataStore->removeEntry("description"); } metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "description", KisMetaData::Value(QVariant(description)))); } QString license = document->documentInfo()->aboutInfo("license"); if (!license.isEmpty()) { if (metaDataStore->containsEntry("rights")) { metaDataStore->removeEntry("rights"); } metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "rights", KisMetaData::Value(QVariant(license)))); } QString date = document->documentInfo()->aboutInfo("date"); if (!date.isEmpty() && !metaDataStore->containsEntry("rights")) { metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "date", KisMetaData::Value(QVariant(date)))); } } if (options.storeAuthor) { QString author = document->documentInfo()->authorInfo("creator"); if (!author.isEmpty()) { if (!document->documentInfo()->authorContactInfo().isEmpty()) { QString contact = document->documentInfo()->authorContactInfo().at(0); if (!contact.isEmpty()) { author = author+"("+contact+")"; } } if (metaDataStore->containsEntry("creator")) { metaDataStore->removeEntry("creator"); } metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "creator", KisMetaData::Value(QVariant(author)))); } } KisImageBuilder_Result res = kpc.buildFile(io, l, options, metaDataStore.data()); if (res == KisImageBuilder_RESULT_OK) { return KisImportExportFilter::OK; } dbgFile << " Result =" << res; return KisImportExportFilter::InternalError; } KisPropertiesConfigurationSP KisJPEGExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("progressive", false); cfg->setProperty("quality", 80); cfg->setProperty("forceSRGB", false); cfg->setProperty("saveProfile", true); cfg->setProperty("optimize", true); cfg->setProperty("smoothing", 0); cfg->setProperty("baseline", true); cfg->setProperty("subsampling", 0); cfg->setProperty("exif", true); cfg->setProperty("iptc", true); cfg->setProperty("xmp", true); cfg->setProperty("storeAuthor", false); cfg->setProperty("storeMetaData", false); KoColor fill_color(KoColorSpaceRegistry::instance()->rgb8()); fill_color = KoColor(); fill_color.fromQColor(Qt::white); QVariant v; v.setValue(fill_color); cfg->setProperty("transparencyFillcolor", v); cfg->setProperty("filters", ""); return cfg; } KisConfigWidget *KisJPEGExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisWdgOptionsJPEG(parent); } void KisJPEGExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ExifCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(CMYKAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "JPEG"); } KisWdgOptionsJPEG::KisWdgOptionsJPEG(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); metaDataFilters->setModel(&m_filterRegistryModel); qualityLevel->setRange(0, 100, 0); qualityLevel->setSuffix("%"); smoothLevel->setRange(0, 100, 0); smoothLevel->setSuffix("%"); } void KisWdgOptionsJPEG::setConfiguration(const KisPropertiesConfigurationSP cfg) { progressive->setChecked(cfg->getBool("progressive", false)); qualityLevel->setValue(cfg->getInt("quality", 80)); optimize->setChecked(cfg->getBool("optimize", true)); smoothLevel->setValue(cfg->getInt("smoothing", 0)); baseLineJPEG->setChecked(cfg->getBool("baseline", true)); subsampling->setCurrentIndex(cfg->getInt("subsampling", 0)); exif->setChecked(cfg->getBool("exif", true)); iptc->setChecked(cfg->getBool("iptc", true)); xmp->setChecked(cfg->getBool("xmp", true)); chkForceSRGB->setVisible(cfg->getBool("is_sRGB")); chkForceSRGB->setChecked(cfg->getBool("forceSRGB", false)); chkSaveProfile->setChecked(cfg->getBool("saveProfile", true)); KoColor background(KoColorSpaceRegistry::instance()->rgb8()); background.fromQColor(Qt::white); bnTransparencyFillColor->setDefaultColor(background); bnTransparencyFillColor->setColor(cfg->getColor("transparencyFillcolor", background)); chkAuthor->setChecked(cfg->getBool("storeAuthor", false)); chkMetaData->setChecked(cfg->getBool("storeMetaData", false)); m_filterRegistryModel.setEnabledFilters(cfg->getString("filters").split(',')); } KisPropertiesConfigurationSP KisWdgOptionsJPEG::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); QVariant transparencyFillcolor; transparencyFillcolor.setValue(bnTransparencyFillColor->color()); cfg->setProperty("progressive", progressive->isChecked()); cfg->setProperty("quality", (int)qualityLevel->value()); cfg->setProperty("forceSRGB", chkForceSRGB->isChecked()); cfg->setProperty("saveProfile", chkSaveProfile->isChecked()); cfg->setProperty("optimize", optimize->isChecked()); cfg->setProperty("smoothing", (int)smoothLevel->value()); cfg->setProperty("baseline", baseLineJPEG->isChecked()); cfg->setProperty("subsampling", subsampling->currentIndex()); cfg->setProperty("exif", exif->isChecked()); cfg->setProperty("iptc", iptc->isChecked()); cfg->setProperty("xmp", xmp->isChecked()); cfg->setProperty("transparencyFillcolor", transparencyFillcolor); cfg->setProperty("storeAuthor", chkAuthor->isChecked()); cfg->setProperty("storeMetaData", chkMetaData->isChecked()); QString enabledFilters; Q_FOREACH (const KisMetaData::Filter* filter, m_filterRegistryModel.enabledFilters()) { enabledFilters = enabledFilters + filter->id() + ','; } cfg->setProperty("filters", enabledFilters); return cfg; } #include diff --git a/plugins/impex/jpeg/kis_jpeg_export.h b/plugins/impex/jpeg/kis_jpeg_export.h index fdc12da1e7..624f9cea68 100644 --- a/plugins/impex/jpeg/kis_jpeg_export.h +++ b/plugins/impex/jpeg/kis_jpeg_export.h @@ -1,58 +1,58 @@ /* * Copyright (c) 2005 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_JPEG_EXPORT_H_ #define _KIS_JPEG_EXPORT_H_ #include #include #include #include "ui_kis_wdg_options_jpeg.h" -#include -#include +#include +#include class KisWdgOptionsJPEG : public KisConfigWidget, public Ui::WdgOptionsJPEG { Q_OBJECT public: KisWdgOptionsJPEG(QWidget *parent); void setConfiguration(const KisPropertiesConfigurationSP cfg) override; KisPropertiesConfigurationSP configuration() const override; private: KisMetaData::FilterRegistryModel m_filterRegistryModel; }; class KisJPEGExport : public KisImportExportFilter { Q_OBJECT public: KisJPEGExport(QObject *parent, const QVariantList &); ~KisJPEGExport() override; public: KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/libkra/kis_kra_load_visitor.cpp b/plugins/impex/libkra/kis_kra_load_visitor.cpp index a25cb0de3c..407b1d5f62 100644 --- a/plugins/impex/libkra/kis_kra_load_visitor.cpp +++ b/plugins/impex/libkra/kis_kra_load_visitor.cpp @@ -1,748 +1,748 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * * 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_kra_load_visitor.h" #include "kis_kra_tags.h" #include "flake/kis_shape_layer.h" #include "flake/KisReferenceImagesLayer.h" #include "KisReferenceImage.h" #include #include #include #include #include #include #include #include #include #include #include // kritaimage -#include -#include +#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_transform_mask_params_factory_registry.h" #include #include #include #include #include "kis_shape_selection.h" #include "kis_colorize_dom_utils.h" #include "kis_dom_utils.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_frames_interface.h" using namespace KRA; QString expandEncodedDirectory(const QString& _intern) { QString intern = _intern; QString result; int pos; while ((pos = intern.indexOf('/')) != -1) { if (QChar(intern.at(0)).isDigit()) result += "part"; result += intern.left(pos + 1); // copy numbers (or "pictures") + "/" intern = intern.mid(pos + 1); // remove the dir we just processed } if (!intern.isEmpty() && QChar(intern.at(0)).isDigit()) result += "part"; result += intern; return result; } KisKraLoadVisitor::KisKraLoadVisitor(KisImageSP image, KoStore *store, KoShapeControllerBase *shapeController, QMap &layerFilenames, QMap &keyframeFilenames, const QString & name, int syntaxVersion) : KisNodeVisitor() , m_image(image) , m_store(store) , m_external(false) , m_layerFilenames(layerFilenames) , m_keyframeFilenames(keyframeFilenames) , m_name(name) , m_shapeController(shapeController) { m_store->pushDirectory(); if (m_name.startsWith("/")) { m_name.remove(0, 1); } if (!m_store->enterDirectory(m_name)) { QStringList directories = m_store->directoryList(); dbgKrita << directories; if (directories.size() > 0) { dbgFile << "Could not locate the directory, maybe some encoding issue? Grab the first directory, that'll be the image one." << m_name << directories; m_name = directories.first(); } else { dbgFile << "Could not enter directory" << m_name << ", probably an old-style file with 'part' added."; m_name = expandEncodedDirectory(m_name); } } else { m_store->popDirectory(); } m_syntaxVersion = syntaxVersion; } void KisKraLoadVisitor::setExternalUri(const QString &uri) { m_external = true; m_uri = uri; } bool KisKraLoadVisitor::visit(KisExternalLayer * layer) { bool result = false; if (auto *referencesLayer = dynamic_cast(layer)) { Q_FOREACH(KoShape *shape, referencesLayer->shapes()) { auto *reference = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(reference, false); while (!reference->loadImage(m_store)) { if (reference->embed()) { m_errorMessages << i18n("Could not load embedded reference image %1 ", reference->internalFile()); break; } else { QString msg = i18nc( "@info", "A reference image linked to an external file could not be loaded.\n\n" "Path: %1\n\n" "Do you want to select another location?", reference->filename()); int locateManually = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); QString url; if (locateManually == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); url = dialog.filename(); } if (url.isEmpty()) { break; } else { reference->setFilename(url); } } } } } else if (KisShapeLayer *shapeLayer = dynamic_cast(layer)) { if (!loadMetaData(layer)) { return false; } m_store->pushDirectory(); m_store->enterDirectory(getLocation(layer, DOT_SHAPE_LAYER)) ; result = shapeLayer->loadLayer(m_store); m_store->popDirectory(); } result = visitAll(layer) && result; return result; } bool KisKraLoadVisitor::visit(KisPaintLayer *layer) { loadNodeKeyframes(layer); dbgFile << "Visit: " << layer->name() << " colorSpace: " << layer->colorSpace()->id(); if (!loadPaintDevice(layer->paintDevice(), getLocation(layer))) { return false; } if (!loadProfile(layer->paintDevice(), getLocation(layer, DOT_ICC))) { return false; } if (!loadMetaData(layer)) { return false; } if (m_syntaxVersion == 1) { // Check whether there is a file with a .mask extension in the // layer directory, if so, it's an old-style transparency mask // that should be converted. QString location = getLocation(layer, ".mask"); if (m_store->open(location)) { KisSelectionSP selection = KisSelectionSP(new KisSelection()); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); if (!pixelSelection->read(m_store->device())) { pixelSelection->disconnect(); } else { KisTransparencyMask* mask = new KisTransparencyMask(); mask->setSelection(selection); m_image->addNode(mask, layer, layer->firstChild()); } m_store->close(); } } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGroupLayer *layer) { if (*layer->colorSpace() != *m_image->colorSpace()) { layer->resetCache(m_image->colorSpace()); } if (!loadMetaData(layer)) { return false; } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisAdjustmentLayer* layer) { loadNodeKeyframes(layer); // Adjustmentlayers are tricky: there's the 1.x style and the 2.x // style, which has selections with selection components bool result = true; if (m_syntaxVersion == 1) { KisSelectionSP selection = new KisSelection(); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); result = loadPaintDevice(pixelSelection, getLocation(layer, ".selection")); layer->setInternalSelection(selection); } else if (m_syntaxVersion == 2) { result = loadSelection(getLocation(layer), layer->internalSelection()); } else { // We use the default, empty selection } if (!loadMetaData(layer)) { return false; } loadFilterConfiguration(layer->filter().data(), getLocation(layer, DOT_FILTERCONFIG)); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGeneratorLayer *layer) { if (!loadMetaData(layer)) { return false; } bool result = true; loadNodeKeyframes(layer); result = loadSelection(getLocation(layer), layer->internalSelection()); result = loadFilterConfiguration(layer->filter().data(), getLocation(layer, DOT_FILTERCONFIG)); layer->update(); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisCloneLayer *layer) { if (!loadMetaData(layer)) { return false; } // the layer might have already been lazily initialized // from the mask loading code if (layer->copyFrom()) { return true; } KisNodeSP srcNode = layer->copyFromInfo().findNode(m_image->rootLayer()); KisLayerSP srcLayer = qobject_cast(srcNode.data()); Q_ASSERT(srcLayer); layer->setCopyFrom(srcLayer); // Clone layers have no data except for their masks bool result = visitAll(layer); return result; } void KisKraLoadVisitor::initSelectionForMask(KisMask *mask) { KisLayer *cloneLayer = dynamic_cast(mask->parent().data()); if (cloneLayer) { // the clone layers should be initialized out of order // and lazily, because their original() is still not // initialized cloneLayer->accept(*this); } KisLayer *parentLayer = qobject_cast(mask->parent().data()); // the KisKraLoader must have already set the parent for us Q_ASSERT(parentLayer); mask->initSelection(parentLayer); } bool KisKraLoadVisitor::visit(KisFilterMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); bool result = true; result = loadSelection(getLocation(mask), mask->selection()); result = loadFilterConfiguration(mask->filter().data(), getLocation(mask, DOT_FILTERCONFIG)); return result; } bool KisKraLoadVisitor::visit(KisTransformMask *mask) { QString location = getLocation(mask, DOT_TRANSFORMCONFIG); if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement rootElement = doc.documentElement(); QDomElement main; if (!KisDomUtils::findOnlyElement(rootElement, "main", &main/*, &m_errorMessages*/)) { return false; } QString id = main.attribute("id", "not-valid"); if (id == "not-valid") { m_errorMessages << i18n("Could not load \"id\" of the transform mask"); return false; } QDomElement data; if (!KisDomUtils::findOnlyElement(rootElement, "data", &data, &m_errorMessages)) { return false; } KisTransformMaskParamsInterfaceSP params = KisTransformMaskParamsFactoryRegistry::instance()->createParams(id, data); if (!params) { m_errorMessages << i18n("Could not create transform mask params"); return false; } mask->setTransformParams(params); loadNodeKeyframes(mask); params->clearChangedFlag(); return true; } } return false; } bool KisKraLoadVisitor::visit(KisTransparencyMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisSelectionMask *mask) { initSelectionForMask(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisColorizeMask *mask) { m_store->pushDirectory(); QString location = getLocation(mask, DOT_COLORIZE_MASK); m_store->enterDirectory(location) ; QByteArray data; if (!m_store->extractFile("content.xml", data)) return false; QDomDocument doc; if (!doc.setContent(data)) return false; QVector strokes; if (!KisDomUtils::loadValue(doc.documentElement(), COLORIZE_KEYSTROKES_SECTION, &strokes, mask->colorSpace())) return false; int i = 0; Q_FOREACH (const KisLazyFillTools::KeyStroke &stroke, strokes) { const QString fileName = QString("%1_%2").arg(COLORIZE_KEYSTROKE).arg(i++); loadPaintDevice(stroke.dev, fileName); } mask->setKeyStrokesDirect(QList::fromVector(strokes)); loadPaintDevice(mask->coloringProjection(), COLORIZE_COLORING_DEVICE); mask->resetCache(); m_store->popDirectory(); return true; } QStringList KisKraLoadVisitor::errorMessages() const { return m_errorMessages; } QStringList KisKraLoadVisitor::warningMessages() const { return m_warningMessages; } struct SimpleDevicePolicy { bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->read(stream); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->setDefaultPixel(defaultPixel); } }; struct FramedDevicePolicy { FramedDevicePolicy(int frameId) : m_frameId(frameId) {} bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->framesInterface()->readFrame(stream, m_frameId); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->framesInterface()->setFrameDefaultPixel(defaultPixel, m_frameId); } int m_frameId; }; bool KisKraLoadVisitor::loadPaintDevice(KisPaintDeviceSP device, const QString& location) { // Layer data KisPaintDeviceFramesInterface *frameInterface = device->framesInterface(); QList frames; if (frameInterface) { frames = device->framesInterface()->frames(); } if (!frameInterface || frames.count() <= 1) { return loadPaintDeviceFrame(device, location, SimpleDevicePolicy()); } else { KisRasterKeyframeChannel *keyframeChannel = device->keyframeChannel(); for (int i = 0; i < frames.count(); i++) { int id = frames[i]; if (keyframeChannel->frameFilename(id).isEmpty()) { m_warningMessages << i18n("Could not find keyframe pixel data for frame %1 in %2.", id, location); } else { Q_ASSERT(!keyframeChannel->frameFilename(id).isEmpty()); QString frameFilename = getLocation(keyframeChannel->frameFilename(id)); Q_ASSERT(!frameFilename.isEmpty()); if (!loadPaintDeviceFrame(device, frameFilename, FramedDevicePolicy(id))) { m_warningMessages << i18n("Could not load keyframe pixel data for frame %1 in %2.", id, location); } } } } return true; } template bool KisKraLoadVisitor::loadPaintDeviceFrame(KisPaintDeviceSP device, const QString &location, DevicePolicy policy) { { const int pixelSize = device->colorSpace()->pixelSize(); KoColor color(Qt::transparent, device->colorSpace()); if (m_store->open(location + ".defaultpixel")) { if (m_store->size() == pixelSize) { m_store->read((char*)color.data(), pixelSize); } m_store->close(); } policy.setDefaultPixel(device, color); } if (m_store->open(location)) { if (!policy.read(device, m_store->device())) { m_warningMessages << i18n("Could not read pixel data: %1.", location); device->disconnect(); m_store->close(); return true; } m_store->close(); } else { m_warningMessages << i18n("Could not load pixel data: %1.", location); return true; } return true; } bool KisKraLoadVisitor::loadProfile(KisPaintDeviceSP device, const QString& location) { if (m_store->hasFile(location)) { m_store->open(location); QByteArray data; data.resize(m_store->size()); dbgFile << "Data to load: " << m_store->size() << " from " << location << " with color space " << device->colorSpace()->id(); int read = m_store->read(data.data(), m_store->size()); dbgFile << "Profile size: " << data.size() << " " << m_store->atEnd() << " " << m_store->device()->bytesAvailable() << " " << read; m_store->close(); // Create a colorspace with the embedded profile const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(device->colorSpace()->colorModelId().id(), device->colorSpace()->colorDepthId().id(), data); if (device->setProfile(profile)) { return true; } } m_warningMessages << i18n("Could not load profile: %1.", location); return true; } bool KisKraLoadVisitor::loadFilterConfiguration(KisFilterConfigurationSP kfc, const QString& location) { if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement e = doc.documentElement(); if (e.tagName() == "filterconfig") { kfc->fromLegacyXML(e); } else { kfc->fromXML(e); } loadDeprecatedFilter(kfc); return true; } } m_warningMessages << i18n("Could not filter configuration %1.", location); return true; } bool KisKraLoadVisitor::loadMetaData(KisNode* node) { KisLayer* layer = qobject_cast(node); if (!layer) return true; KisMetaData::IOBackend* backend = KisMetaData::IOBackendRegistry::instance()->get("xmp"); if (!backend || !backend->supportLoading()) { if (backend) dbgFile << "Backend " << backend->id() << " does not support loading."; else dbgFile << "Could not load the XMP backend at all"; return true; } QString location = getLocation(node, QString(".") + backend->id() + DOT_METADATA); dbgFile << "going to load " << backend->id() << ", " << backend->name() << " from " << location; if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); QBuffer buffer(&data); if (!backend->loadFrom(layer->metaData(), &buffer)) { m_warningMessages << i18n("Could not load metadata for layer %1.", layer->name()); } } return true; } bool KisKraLoadVisitor::loadSelection(const QString& location, KisSelectionSP dstSelection) { // by default the selection is expected to be fully transparent { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); KoColor transparent(Qt::transparent, pixelSelection->colorSpace()); pixelSelection->setDefaultPixel(transparent); } // Pixel selection bool result = true; QString pixelSelectionLocation = location + DOT_PIXEL_SELECTION; if (m_store->hasFile(pixelSelectionLocation)) { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); result = loadPaintDevice(pixelSelection, pixelSelectionLocation); if (!result) { m_warningMessages << i18n("Could not load raster selection %1.", location); } pixelSelection->invalidateOutlineCache(); } // Shape selection QString shapeSelectionLocation = location + DOT_SHAPE_SELECTION; if (m_store->hasFile(shapeSelectionLocation + "/content.svg") || m_store->hasFile(shapeSelectionLocation + "/content.xml")) { m_store->pushDirectory(); m_store->enterDirectory(shapeSelectionLocation) ; KisShapeSelection* shapeSelection = new KisShapeSelection(m_shapeController, m_image, dstSelection); dstSelection->setShapeSelection(shapeSelection); result = shapeSelection->loadSelection(m_store); m_store->popDirectory(); if (!result) { m_warningMessages << i18n("Could not load vector selection %1.", location); } } return true; } QString KisKraLoadVisitor::getLocation(KisNode* node, const QString& suffix) { return getLocation(m_layerFilenames[node], suffix); } QString KisKraLoadVisitor::getLocation(const QString &filename, const QString& suffix) { QString location = m_external ? QString() : m_uri; location += m_name + LAYER_PATH + filename + suffix; return location; } void KisKraLoadVisitor::loadNodeKeyframes(KisNode *node) { if (!m_keyframeFilenames.contains(node)) return; node->enableAnimation(); const QString &location = getLocation(m_keyframeFilenames[node]); if (!m_store->open(location)) { m_errorMessages << i18n("Could not load keyframes from %1.", location); return; } QString errorMsg; int errorLine; int errorColumn; QDomDocument dom; bool ok = dom.setContent(m_store->device(), &errorMsg, &errorLine, &errorColumn); m_store->close(); if (!ok) { m_errorMessages << i18n("parsing error in the keyframe file %1 at line %2, column %3\nError message: %4", location, errorLine, errorColumn, i18n(errorMsg.toUtf8())); return; } QDomElement root = dom.firstChildElement(); for (QDomElement child = root.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (child.nodeName().toUpper() == "CHANNEL") { QString id = child.attribute("name"); KisKeyframeChannel *channel = node->getKeyframeChannel(id, true); if (!channel) { m_warningMessages << i18n("unknown keyframe channel type: %1 in %2", id, location); continue; } channel->loadXML(child); } } } void KisKraLoadVisitor::loadDeprecatedFilter(KisFilterConfigurationSP cfg) { if (cfg->getString("legacy") == "left edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "yFall"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "right edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "yGrowth"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "top edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "xGrowth"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "bottom edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "xFall"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } } diff --git a/plugins/impex/libkra/kis_kra_save_visitor.cpp b/plugins/impex/libkra/kis_kra_save_visitor.cpp index da17099656..a61f5ba456 100644 --- a/plugins/impex/libkra/kis_kra_save_visitor.cpp +++ b/plugins/impex/libkra/kis_kra_save_visitor.cpp @@ -1,549 +1,549 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2007-2008 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_kra_save_visitor.h" #include "kis_kra_tags.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 "lazybrush/kis_colorize_mask.h" #include #include -#include -#include +#include +#include #include "kis_config.h" #include "kis_store_paintdevice_writer.h" #include "flake/kis_shape_selection.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_frames_interface.h" #include "lazybrush/kis_lazy_fill_tools.h" #include #include "kis_colorize_dom_utils.h" #include "kis_dom_utils.h" using namespace KRA; KisKraSaveVisitor::KisKraSaveVisitor(KoStore *store, const QString & name, QMap nodeFileNames) : KisNodeVisitor() , m_store(store) , m_external(false) , m_name(name) , m_nodeFileNames(nodeFileNames) , m_writer(new KisStorePaintDeviceWriter(store)) { } KisKraSaveVisitor::~KisKraSaveVisitor() { delete m_writer; } void KisKraSaveVisitor::setExternalUri(const QString &uri) { m_external = true; m_uri = uri; } bool KisKraSaveVisitor::visit(KisExternalLayer * layer) { bool result = false; if (auto* referencesLayer = dynamic_cast(layer)) { result = true; Q_FOREACH(KoShape *shape, referencesLayer->shapes()) { auto *reference = dynamic_cast(shape); KIS_ASSERT_RECOVER_RETURN_VALUE(reference, false); bool saved = reference->saveImage(m_store); if (!saved) { m_errorMessages << i18n("Failed to save reference image %1.", reference->internalFile()); result = false; } } } else if (KisShapeLayer *shapeLayer = dynamic_cast(layer)) { if (!saveMetaData(layer)) { m_errorMessages << i18n("Failed to save the metadata for layer %1.", layer->name()); return false; } m_store->pushDirectory(); QString location = getLocation(layer, DOT_SHAPE_LAYER); result = m_store->enterDirectory(location); if (!result) { m_errorMessages << i18n("Failed to open %1.", location); } else { result = shapeLayer->saveLayer(m_store); m_store->popDirectory(); } } else if (KisFileLayer *fileLayer = dynamic_cast(layer)) { Q_UNUSED(fileLayer); // We don't save data for file layers, but we still want to save the masks. result = true; } return result && visitAllInverse(layer); } bool KisKraSaveVisitor::visit(KisPaintLayer *layer) { if (!savePaintDevice(layer->paintDevice(), getLocation(layer))) { m_errorMessages << i18n("Failed to save the pixel data for layer %1.", layer->name()); return false; } if (!saveAnnotations(layer)) { m_errorMessages << i18n("Failed to save the annotations for layer %1.", layer->name()); return false; } if (!saveMetaData(layer)) { m_errorMessages << i18n("Failed to save the metadata for layer %1.", layer->name()); return false; } return visitAllInverse(layer); } bool KisKraSaveVisitor::visit(KisGroupLayer *layer) { if (!saveMetaData(layer)) { m_errorMessages << i18n("Failed to save the metadata for layer %1.", layer->name()); return false; } return visitAllInverse(layer); } bool KisKraSaveVisitor::visit(KisAdjustmentLayer* layer) { if (!layer->filter()) { m_errorMessages << i18n("Failed to save the filter layer %1: it has no filter.", layer->name()); return false; } if (!saveSelection(layer)) { m_errorMessages << i18n("Failed to save the selection for filter layer %1.", layer->name()); return false; } if (!saveFilterConfiguration(layer)) { m_errorMessages << i18n("Failed to save the filter configuration for filter layer %1.", layer->name()); return false; } if (!saveMetaData(layer)) { m_errorMessages << i18n("Failed to save the metadata for layer %1.", layer->name()); return false; } return visitAllInverse(layer); } bool KisKraSaveVisitor::visit(KisGeneratorLayer * layer) { if (!saveSelection(layer)) { m_errorMessages << i18n("Failed to save the selection for layer %1.", layer->name()); return false; } if (!saveFilterConfiguration(layer)) { m_errorMessages << i18n("Failed to save the generator configuration for layer %1.", layer->name()); return false; } if (!saveMetaData(layer)) { m_errorMessages << i18n("Failed to save the metadata for layer %1.", layer->name()); return false; } return visitAllInverse(layer); } bool KisKraSaveVisitor::visit(KisCloneLayer *layer) { // Clone layers do not have a profile if (!saveMetaData(layer)) { m_errorMessages << i18n("Failed to save the metadata for layer %1.", layer->name()); return false; } return visitAllInverse(layer); } bool KisKraSaveVisitor::visit(KisFilterMask *mask) { if (!mask->filter()) { m_errorMessages << i18n("Failed to save filter mask %1. It has no filter.", mask->name()); return false; } if (!saveSelection(mask)) { m_errorMessages << i18n("Failed to save the selection for filter mask %1.", mask->name()); return false; } if (!saveFilterConfiguration(mask)) { m_errorMessages << i18n("Failed to save the filter configuration for filter mask %1.", mask->name()); return false; } return true; } bool KisKraSaveVisitor::visit(KisTransformMask *mask) { QDomDocument doc("transform_params"); QDomElement root = doc.createElement("transform_params"); QDomElement main = doc.createElement("main"); main.setAttribute("id", mask->transformParams()->id()); QDomElement data = doc.createElement("data"); mask->transformParams()->toXML(&data); doc.appendChild(root); root.appendChild(main); root.appendChild(data); QString location = getLocation(mask, DOT_TRANSFORMCONFIG); if (m_store->open(location)) { QByteArray a = doc.toByteArray(); bool retval = m_store->write(a) == a.size(); if (!retval) { warnFile << "Could not write transform mask configuration"; } if (!m_store->close()) { warnFile << "Could not close store after writing transform mask configuration"; retval = false; } return retval; } return false; } bool KisKraSaveVisitor::visit(KisTransparencyMask *mask) { if (!saveSelection(mask)) { m_errorMessages << i18n("Failed to save the selection for transparency mask %1.", mask->name()); return false; } return true; } bool KisKraSaveVisitor::visit(KisSelectionMask *mask) { if (!saveSelection(mask)) { m_errorMessages << i18n("Failed to save the selection for local selection %1.", mask->name()); return false; } return true; } bool KisKraSaveVisitor::visit(KisColorizeMask *mask) { m_store->pushDirectory(); QString location = getLocation(mask, DOT_COLORIZE_MASK); bool result = m_store->enterDirectory(location); if (!result) { m_errorMessages << i18n("Failed to open %1.", location); return false; } if (!m_store->open("content.xml")) return false; KoStoreDevice storeDev(m_store); QDomDocument doc("doc"); QDomElement root = doc.createElement("colorize"); doc.appendChild(root); KisDomUtils::saveValue(&root, COLORIZE_KEYSTROKES_SECTION, QVector::fromList(mask->fetchKeyStrokesDirect())); QTextStream stream(&storeDev); stream << doc; if (!m_store->close()) return false; int i = 0; Q_FOREACH (const KisLazyFillTools::KeyStroke &stroke, mask->fetchKeyStrokesDirect()) { const QString fileName = QString("%1_%2").arg(COLORIZE_KEYSTROKE).arg(i++); savePaintDevice(stroke.dev, fileName); } savePaintDevice(mask->coloringProjection(), COLORIZE_COLORING_DEVICE); m_store->popDirectory(); return true; } QStringList KisKraSaveVisitor::errorMessages() const { return m_errorMessages; } struct SimpleDevicePolicy { bool write(KisPaintDeviceSP dev, KisPaintDeviceWriter &store) { return dev->write(store); } KoColor defaultPixel(KisPaintDeviceSP dev) const { return dev->defaultPixel(); } }; struct FramedDevicePolicy { FramedDevicePolicy(int frameId) : m_frameId(frameId) {} bool write(KisPaintDeviceSP dev, KisPaintDeviceWriter &store) { return dev->framesInterface()->writeFrame(store, m_frameId); } KoColor defaultPixel(KisPaintDeviceSP dev) const { return dev->framesInterface()->frameDefaultPixel(m_frameId); } int m_frameId; }; bool KisKraSaveVisitor::savePaintDevice(KisPaintDeviceSP device, QString location) { // Layer data KisConfig cfg(true); m_store->setCompressionEnabled(cfg.compressKra()); KisPaintDeviceFramesInterface *frameInterface = device->framesInterface(); QList frames; if (frameInterface) { frames = frameInterface->frames(); } if (!frameInterface || frames.count() <= 1) { savePaintDeviceFrame(device, location, SimpleDevicePolicy()); } else { KisRasterKeyframeChannel *keyframeChannel = device->keyframeChannel(); for (int i = 0; i < frames.count(); i++) { int id = frames[i]; QString frameFilename = getLocation(keyframeChannel->frameFilename(id)); Q_ASSERT(!frameFilename.isEmpty()); if (!savePaintDeviceFrame(device, frameFilename, FramedDevicePolicy(id))) { return false; } } } m_store->setCompressionEnabled(true); return true; } template bool KisKraSaveVisitor::savePaintDeviceFrame(KisPaintDeviceSP device, QString location, DevicePolicy policy) { if (m_store->open(location)) { if (!policy.write(device, *m_writer)) { device->disconnect(); m_store->close(); return false; } m_store->close(); } if (m_store->open(location + ".defaultpixel")) { m_store->write((char*)policy.defaultPixel(device).data(), device->colorSpace()->pixelSize()); m_store->close(); } return true; } bool KisKraSaveVisitor::saveAnnotations(KisLayer* layer) { if (!layer) return false; if (!layer->paintDevice()) return false; if (!layer->paintDevice()->colorSpace()) return false; if (layer->paintDevice()->colorSpace()->profile()) { const KoColorProfile *profile = layer->paintDevice()->colorSpace()->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) { // save layer profile if (m_store->open(getLocation(layer, DOT_ICC))) { m_store->write(annotation->annotation()); m_store->close(); } else { return false; } } } return true; } bool KisKraSaveVisitor::saveSelection(KisNode* node) { KisSelectionSP selection; if (node->inherits("KisMask")) { selection = static_cast(node)->selection(); } else if (node->inherits("KisAdjustmentLayer")) { selection = static_cast(node)->internalSelection(); } else if (node->inherits("KisGeneratorLayer")) { selection = static_cast(node)->internalSelection(); } else { return false; } bool retval = true; if (selection->hasPixelSelection()) { KisPaintDeviceSP dev = selection->pixelSelection(); if (!savePaintDevice(dev, getLocation(node, DOT_PIXEL_SELECTION))) { m_errorMessages << i18n("Failed to save the pixel selection data for layer %1.", node->name()); retval = false; } } if (selection->hasShapeSelection()) { m_store->pushDirectory(); retval = m_store->enterDirectory(getLocation(node, DOT_SHAPE_SELECTION)); if (retval) { KisShapeSelection* shapeSelection = dynamic_cast(selection->shapeSelection()); if (!shapeSelection) { retval = false; } if (retval && !shapeSelection->saveSelection(m_store)) { m_errorMessages << i18n("Failed to save the vector selection data for layer %1.", node->name()); retval = false; } } m_store->popDirectory(); } return retval; } bool KisKraSaveVisitor::saveFilterConfiguration(KisNode* node) { KisNodeFilterInterface *filterInterface = dynamic_cast(node); KisFilterConfigurationSP filter; if (filterInterface) { filter = filterInterface->filter(); } bool retval = false; if (filter) { QString location = getLocation(node, DOT_FILTERCONFIG); if (m_store->open(location)) { QString s = filter->toXML(); retval = (m_store->write(s.toUtf8(), qstrlen(s.toUtf8())) == qstrlen(s.toUtf8())); m_store->close(); } } return retval; } bool KisKraSaveVisitor::saveMetaData(KisNode* node) { if (!node->inherits("KisLayer")) return true; KisMetaData::Store* metadata = (static_cast(node))->metaData(); if (metadata->isEmpty()) return true; // Serialize all the types of metadata there are KisMetaData::IOBackend* backend = KisMetaData::IOBackendRegistry::instance()->get("xmp"); if (!backend->supportSaving()) { dbgFile << "Backend " << backend->id() << " does not support saving."; return false; } QString location = getLocation(node, QString(".") + backend->id() + DOT_METADATA); dbgFile << "going to save " << backend->id() << ", " << backend->name() << " to " << location; QBuffer buffer; // not that the metadata backends every return anything but true... bool retval = backend->saveTo(metadata, &buffer); if (!retval) { m_errorMessages << i18n("The metadata backend failed to save the metadata for %1", node->name()); } else { QByteArray data = buffer.data(); dbgFile << "\t information size is" << data.size(); if (data.size() > 0 && m_store->open(location)) { retval = m_store->write(data, data.size()); m_store->close(); } if (!retval) { m_errorMessages << i18n("Could not write for %1 metadata to the file.", node->name()); } } return retval; } QString KisKraSaveVisitor::getLocation(KisNode* node, const QString& suffix) { Q_ASSERT(m_nodeFileNames.contains(node)); return getLocation(m_nodeFileNames[node], suffix); } QString KisKraSaveVisitor::getLocation(const QString &filename, const QString& suffix) { QString location = m_external ? QString() : m_uri; location += m_name + LAYER_PATH + filename + suffix; return location; } diff --git a/plugins/impex/ora/ora_save_context.cc b/plugins/impex/ora/ora_save_context.cc index 2a524610de..0198df6188 100644 --- a/plugins/impex/ora/ora_save_context.cc +++ b/plugins/impex/ora/ora_save_context.cc @@ -1,59 +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 #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::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/ora/ora_save_context.h b/plugins/impex/ora/ora_save_context.h index 947ea9104b..482eff5cbc 100644 --- a/plugins/impex/ora/ora_save_context.h +++ b/plugins/impex/ora/ora_save_context.h @@ -1,38 +1,38 @@ /* * 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. */ #ifndef _ORA_SAVE_CONTEXT_H_ #define _ORA_SAVE_CONTEXT_H_ class KoStore; -#include +#include #include "kis_open_raster_save_context.h" class OraSaveContext : public KisOpenRasterSaveContext { public: OraSaveContext(KoStore* _store); ~OraSaveContext() override{} QString saveDeviceData(KisPaintDeviceSP dev, KisMetaData::Store *metaData, const QRect &imageRect, const qreal xRes, const qreal yRes) override; void saveStack(const QDomDocument& doc) override; private: int m_id; KoStore* m_store; }; #endif diff --git a/plugins/impex/png/kis_png_export.cc b/plugins/impex/png/kis_png_export.cc index 7ccc1c4a7c..ef84984e19 100644 --- a/plugins/impex/png/kis_png_export.cc +++ b/plugins/impex/png/kis_png_export.cc @@ -1,229 +1,229 @@ /* * Copyright (c) 2005 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_export.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_png_converter.h" #include K_PLUGIN_FACTORY_WITH_JSON(KisPNGExportFactory, "krita_png_export.json", registerPlugin();) KisPNGExport::KisPNGExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPNGExport::~KisPNGExport() { } KisImportExportFilter::ConversionStatus KisPNGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { KisImageSP image = document->savingImage(); KisPNGOptions options; options.alpha = configuration->getBool("alpha", true); options.interlace = configuration->getBool("interlaced", false); options.compression = configuration->getInt("compression", 3); options.tryToSaveAsIndexed = configuration->getBool("indexed", false); KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromQColor(Qt::white); options.transparencyFillColor = configuration->getColor("transparencyFillcolor", c).toQColor(); options.saveSRGBProfile = configuration->getBool("saveSRGBProfile", false); options.forceSRGB = configuration->getBool("forceSRGB", true); options.storeAuthor = configuration->getBool("storeAuthor", false); options.storeMetaData = configuration->getBool("storeMetaData", false); vKisAnnotationSP_it beginIt = image->beginAnnotations(); vKisAnnotationSP_it endIt = image->endAnnotations(); KisExifInfoVisitor eIV; eIV.visit(image->rootLayer().data()); KisMetaData::Store *eI = 0; if (eIV.metaDataCount() == 1) { eI = eIV.exifInfo(); } if (eI) { KisMetaData::Store* copy = new KisMetaData::Store(*eI); eI = copy; } KisPNGConverter pngConverter(document); KisImageBuilder_Result res = pngConverter.buildFile(io, image->bounds(), image->xRes(), image->yRes(), image->projection(), beginIt, endIt, options, eI); if (res == KisImageBuilder_RESULT_OK) { delete eI; return KisImportExportFilter::OK; } delete eI; dbgFile << " Result =" << res; return KisImportExportFilter::InternalError; } KisPropertiesConfigurationSP KisPNGExport::defaultConfiguration(const QByteArray &, const QByteArray &) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("alpha", true); cfg->setProperty("indexed", false); cfg->setProperty("compression", 3); cfg->setProperty("interlaced", false); KoColor fill_color(KoColorSpaceRegistry::instance()->rgb8()); fill_color = KoColor(); fill_color.fromQColor(Qt::white); QVariant v; v.setValue(fill_color); cfg->setProperty("transparencyFillcolor", v); cfg->setProperty("saveSRGBProfile", false); cfg->setProperty("forceSRGB", true); cfg->setProperty("storeMetaData", false); cfg->setProperty("storeAuthor", false); return cfg; } KisConfigWidget *KisPNGExport::createConfigurationWidget(QWidget *parent, const QByteArray &, const QByteArray &) const { return new KisWdgOptionsPNG(parent); } void KisPNGExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "PNG"); } void KisWdgOptionsPNG::setConfiguration(const KisPropertiesConfigurationSP cfg) { // the export manager should have prepared some info for us! KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::ImageContainsTransparencyTag)); KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::ColorModelIDTag)); KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::sRGBTag)); const bool isThereAlpha = cfg->getBool(KisImportExportFilter::ImageContainsTransparencyTag); alpha->setChecked(cfg->getBool("alpha", isThereAlpha)); bnTransparencyFillColor->setEnabled(!alpha->isChecked()); if (cfg->getString(KisImportExportFilter::ColorModelIDTag) == RGBAColorModelID.id()) { tryToSaveAsIndexed->setVisible(true); if (alpha->isChecked()) { tryToSaveAsIndexed->setChecked(false); } else { tryToSaveAsIndexed->setChecked(cfg->getBool("indexed", false)); } } else { tryToSaveAsIndexed->setVisible(false); } interlacing->setChecked(cfg->getBool("interlaced", false)); compressionLevel->setValue(cfg->getInt("compression", 3)); compressionLevel->setRange(1, 9, 0); tryToSaveAsIndexed->setVisible(!isThereAlpha); //const bool sRGB = cfg->getBool(KisImportExportFilter::sRGBTag, false); //chkSRGB->setEnabled(sRGB); chkSRGB->setChecked(cfg->getBool("saveSRGBProfile", true)); //chkForceSRGB->setEnabled(!sRGB); chkForceSRGB->setChecked(cfg->getBool("forceSRGB", false)); chkAuthor->setChecked(cfg->getBool("storeAuthor", false)); chkMetaData->setChecked(cfg->getBool("storeMetaData", false)); KoColor background(KoColorSpaceRegistry::instance()->rgb8()); background.fromQColor(Qt::white); bnTransparencyFillColor->setDefaultColor(background); bnTransparencyFillColor->setColor(cfg->getColor("transparencyFillcolor", background)); } KisPropertiesConfigurationSP KisWdgOptionsPNG::configuration() const { KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); bool alpha = this->alpha->isChecked(); bool interlace = interlacing->isChecked(); int compression = (int)compressionLevel->value(); bool tryToSaveAsIndexed = this->tryToSaveAsIndexed->isChecked(); bool saveSRGB = chkSRGB->isChecked(); bool forceSRGB = chkForceSRGB->isChecked(); bool storeAuthor = chkAuthor->isChecked(); bool storeMetaData = chkMetaData->isChecked(); QVariant transparencyFillcolor; transparencyFillcolor.setValue(bnTransparencyFillColor->color()); cfg->setProperty("alpha", alpha); cfg->setProperty("indexed", tryToSaveAsIndexed); cfg->setProperty("compression", compression); cfg->setProperty("interlaced", interlace); cfg->setProperty("transparencyFillcolor", transparencyFillcolor); cfg->setProperty("saveSRGBProfile", saveSRGB); cfg->setProperty("forceSRGB", forceSRGB); cfg->setProperty("storeAuthor", storeAuthor); cfg->setProperty("storeMetaData", storeMetaData); return cfg; } void KisWdgOptionsPNG::on_alpha_toggled(bool checked) { bnTransparencyFillColor->setEnabled(!checked); } #include "kis_png_export.moc" diff --git a/plugins/impex/tiff/kis_dlg_options_tiff.cpp b/plugins/impex/tiff/kis_dlg_options_tiff.cpp index 932a687b9c..0f9729ae0f 100644 --- a/plugins/impex/tiff/kis_dlg_options_tiff.cpp +++ b/plugins/impex/tiff/kis_dlg_options_tiff.cpp @@ -1,120 +1,115 @@ /* * Copyright (c) 2005 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_dlg_options_tiff.h" #include #include #include #include #include #include #include #include #include #include #include #include KisTIFFOptionsWidget::KisTIFFOptionsWidget(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); activated(0); connect(kComboBoxCompressionType, SIGNAL(activated(int)), this, SLOT(activated(int))); connect(flatten, SIGNAL(toggled(bool)), this, SLOT(flattenToggled(bool))); QApplication::restoreOverrideCursor(); setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); } void KisTIFFOptionsWidget::setConfiguration(const KisPropertiesConfigurationSP cfg) { kComboBoxCompressionType->setCurrentIndex(cfg->getInt("compressiontype", 0)); activated(kComboBoxCompressionType->currentIndex()); kComboBoxPredictor->setCurrentIndex(cfg->getInt("predictor", 0)); alpha->setChecked(cfg->getBool("alpha", true)); flatten->setChecked(cfg->getBool("flatten", true)); flattenToggled(flatten->isChecked()); qualityLevel->setValue(cfg->getInt("quality", 80)); compressionLevelDeflate->setValue(cfg->getInt("deflate", 6)); - kComboBoxFaxMode->setCurrentIndex(cfg->getInt("faxmode", 0)); compressionLevelPixarLog->setValue(cfg->getInt("pixarlog", 6)); chkSaveProfile->setChecked(cfg->getBool("saveProfile", true)); if (cfg->getInt("type", -1) == KoChannelInfo::FLOAT16 || cfg->getInt("type", -1) == KoChannelInfo::FLOAT32) { kComboBoxPredictor->removeItem(1); } else { kComboBoxPredictor->removeItem(2); } if (cfg->getBool("isCMYK")) { alpha->setChecked(false); alpha->setEnabled(false); } } KisPropertiesConfigurationSP KisTIFFOptionsWidget::configuration() const { KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); cfg->setProperty("compressiontype", kComboBoxCompressionType->currentIndex()); cfg->setProperty("predictor", kComboBoxPredictor->currentIndex()); cfg->setProperty("alpha", alpha->isChecked()); cfg->setProperty("flatten", flatten->isChecked()); cfg->setProperty("quality", qualityLevel->value()); cfg->setProperty("deflate", compressionLevelDeflate->value()); - cfg->setProperty("faxmode", kComboBoxFaxMode->currentIndex()); cfg->setProperty("pixarlog", compressionLevelPixarLog->value()); cfg->setProperty("saveProfile", chkSaveProfile->isChecked()); return cfg; } void KisTIFFOptionsWidget::activated(int index) { switch (index) { case 1: codecsOptionsStack->setCurrentIndex(1); break; case 2: codecsOptionsStack->setCurrentIndex(2); break; - case 6: - codecsOptionsStack->setCurrentIndex(3); - break; - case 8: + case 4: codecsOptionsStack->setCurrentIndex(4); break; default: codecsOptionsStack->setCurrentIndex(0); } } void KisTIFFOptionsWidget::flattenToggled(bool t) { alpha->setEnabled(t); if (!t) { alpha->setChecked(true); } } diff --git a/plugins/impex/tiff/kis_tiff_converter.cc b/plugins/impex/tiff/kis_tiff_converter.cc index fbd1f8ea46..5b142ee953 100644 --- a/plugins/impex/tiff/kis_tiff_converter.cc +++ b/plugins/impex/tiff/kis_tiff_converter.cc @@ -1,748 +1,742 @@ /* * Copyright (c) 2005-2006 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_tiff_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_tiff_reader.h" #include "kis_tiff_ycbcr_reader.h" #include "kis_buffer_stream.h" #include "kis_tiff_writer_visitor.h" #if TIFFLIB_VERSION < 20111221 typedef size_t tmsize_t; #endif namespace { QPair getColorSpaceForColorType(uint16 sampletype, uint16 color_type, uint16 color_nb_bits, TIFF *image, uint16 &nbchannels, uint16 &extrasamplescount, uint8 &destDepth) { if (color_type == PHOTOMETRIC_MINISWHITE || color_type == PHOTOMETRIC_MINISBLACK) { if (nbchannels == 0) nbchannels = 1; extrasamplescount = nbchannels - 1; // FIX the extrasamples count in case of if (sampletype == SAMPLEFORMAT_IEEEFP) { if (color_nb_bits == 16) { destDepth = 16; return QPair(GrayAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (color_nb_bits == 32) { destDepth = 32; return QPair(GrayAColorModelID.id(), Float32BitsColorDepthID.id()); } } if (color_nb_bits <= 8) { destDepth = 8; return QPair(GrayAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(GrayAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_RGB /*|| color_type == */) { if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count in case of if (sampletype == SAMPLEFORMAT_IEEEFP) { if (color_nb_bits == 16) { destDepth = 16; return QPair(RGBAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (color_nb_bits == 32) { destDepth = 32; return QPair(RGBAColorModelID.id(), Float32BitsColorDepthID.id()); } return QPair(); } else { if (color_nb_bits <= 8) { destDepth = 8; return QPair(RGBAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(RGBAColorModelID.id(), Integer16BitsColorDepthID.id()); } } } else if (color_type == PHOTOMETRIC_YCBCR) { if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count in case of if (color_nb_bits <= 8) { destDepth = 8; return QPair(YCbCrAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(YCbCrAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_SEPARATED) { if (nbchannels == 0) nbchannels = 4; // SEPARATED is in general CMYK but not always, so we check uint16 inkset; if ((TIFFGetField(image, TIFFTAG_INKSET, &inkset) == 0)) { dbgFile << "Image does not define the inkset."; inkset = 2; } if (inkset != INKSET_CMYK) { dbgFile << "Unsupported inkset (right now, only CMYK is supported)"; char** ink_names; uint16 numberofinks; if (TIFFGetField(image, TIFFTAG_INKNAMES, &ink_names) == 1 && TIFFGetField(image, TIFFTAG_NUMBEROFINKS, &numberofinks) == 1) { dbgFile << "Inks are :"; for (uint i = 0; i < numberofinks; i++) { dbgFile << ink_names[i]; } } else { dbgFile << "inknames are not defined !"; // To be able to read stupid adobe files, if there are no information about inks and four channels, then it's a CMYK file : if (nbchannels - extrasamplescount != 4) { return QPair(); } } } if (color_nb_bits <= 8) { destDepth = 8; return QPair(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_CIELAB || color_type == PHOTOMETRIC_ICCLAB) { destDepth = 16; if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count return QPair(LABAColorModelID.id(), Integer16BitsColorDepthID.id()); } else if (color_type == PHOTOMETRIC_PALETTE) { destDepth = 16; if (nbchannels == 0) nbchannels = 2; extrasamplescount = nbchannels - 2; // FIX the extrasamples count // <-- we will convert the index image to RGBA16 as the palette is always on 16bits colors return QPair(RGBAColorModelID.id(), Integer16BitsColorDepthID.id()); } return QPair(); } } KisPropertiesConfigurationSP KisTIFFOptions::toProperties() const { QHash compToIndex; compToIndex[COMPRESSION_NONE] = 0; compToIndex[COMPRESSION_JPEG] = 1; compToIndex[COMPRESSION_DEFLATE] = 2; compToIndex[COMPRESSION_LZW] = 3; - compToIndex[COMPRESSION_JP2000] = 4; - compToIndex[COMPRESSION_CCITTRLE] = 5; - compToIndex[COMPRESSION_CCITTFAX3] = 6; - compToIndex[COMPRESSION_CCITTFAX4] = 7; compToIndex[COMPRESSION_PIXARLOG] = 8; KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("compressiontype", compToIndex.value(compressionType, 0)); cfg->setProperty("predictor", predictor - 1); cfg->setProperty("alpha", alpha); cfg->setProperty("flatten", flatten); cfg->setProperty("quality", jpegQuality); cfg->setProperty("deflate", deflateCompress); - cfg->setProperty("faxmode", faxMode - 1); cfg->setProperty("pixarlog", pixarLogCompress); cfg->setProperty("saveProfile", saveProfile); return cfg; } void KisTIFFOptions::fromProperties(KisPropertiesConfigurationSP cfg) { QHash indexToComp; indexToComp[0] = COMPRESSION_NONE; indexToComp[1] = COMPRESSION_JPEG; indexToComp[2] = COMPRESSION_DEFLATE; indexToComp[3] = COMPRESSION_LZW; - indexToComp[4] = COMPRESSION_JP2000; - indexToComp[5] = COMPRESSION_CCITTRLE; - indexToComp[6] = COMPRESSION_CCITTFAX3; - indexToComp[7] = COMPRESSION_CCITTFAX4; + indexToComp[4] = COMPRESSION_PIXARLOG; + + // old value that might be still stored in a config (remove after Krita 5.0 :) ) indexToComp[8] = COMPRESSION_PIXARLOG; compressionType = indexToComp.value( cfg->getInt("compressiontype", 0), COMPRESSION_NONE); predictor = cfg->getInt("predictor", 0) + 1; alpha = cfg->getBool("alpha", true); flatten = cfg->getBool("flatten", true); jpegQuality = cfg->getInt("quality", 80); deflateCompress = cfg->getInt("deflate", 6); - faxMode = cfg->getInt("faxmode", 0) + 1; pixarLogCompress = cfg->getInt("pixarlog", 6); saveProfile = cfg->getBool("saveProfile", true); } KisTIFFConverter::KisTIFFConverter(KisDocument *doc) { m_doc = doc; m_stop = false; TIFFSetWarningHandler(0); TIFFSetErrorHandler(0); } KisTIFFConverter::~KisTIFFConverter() { } KisImageBuilder_Result KisTIFFConverter::decode(const QString &filename) { dbgFile << "Start decoding TIFF File"; // Opent the TIFF file TIFF *image = 0; if ((image = TIFFOpen(QFile::encodeName(filename), "r")) == 0) { dbgFile << "Could not open the file, either it does not exist, either it is not a TIFF :" << filename; return (KisImageBuilder_RESULT_BAD_FETCH); } do { dbgFile << "Read new sub-image"; KisImageBuilder_Result result = readTIFFDirectory(image); if (result != KisImageBuilder_RESULT_OK) { return result; } } while (TIFFReadDirectory(image)); // Freeing memory TIFFClose(image); return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result KisTIFFConverter::readTIFFDirectory(TIFF* image) { // Read information about the tiff uint32 width, height; if (TIFFGetField(image, TIFFTAG_IMAGEWIDTH, &width) == 0) { dbgFile << "Image does not define its width"; TIFFClose(image); return KisImageBuilder_RESULT_INVALID_ARG; } if (TIFFGetField(image, TIFFTAG_IMAGELENGTH, &height) == 0) { dbgFile << "Image does not define its height"; TIFFClose(image); return KisImageBuilder_RESULT_INVALID_ARG; } float xres; if (TIFFGetField(image, TIFFTAG_XRESOLUTION, &xres) == 0) { dbgFile << "Image does not define x resolution"; // but we don't stop xres = 100; } float yres; if (TIFFGetField(image, TIFFTAG_YRESOLUTION, &yres) == 0) { dbgFile << "Image does not define y resolution"; // but we don't stop yres = 100; } uint16 depth; if ((TIFFGetField(image, TIFFTAG_BITSPERSAMPLE, &depth) == 0)) { dbgFile << "Image does not define its depth"; depth = 1; } uint16 sampletype; if ((TIFFGetField(image, TIFFTAG_SAMPLEFORMAT, &sampletype) == 0)) { dbgFile << "Image does not define its sample type"; sampletype = SAMPLEFORMAT_UINT; } // Determine the number of channels (useful to know if a file has an alpha or not uint16 nbchannels; if (TIFFGetField(image, TIFFTAG_SAMPLESPERPIXEL, &nbchannels) == 0) { dbgFile << "Image has an undefined number of samples per pixel"; nbchannels = 0; } // Get the number of extrasamples and information about them uint16 *sampleinfo = 0, extrasamplescount; if (TIFFGetField(image, TIFFTAG_EXTRASAMPLES, &extrasamplescount, &sampleinfo) == 0) { extrasamplescount = 0; } // Determine the colorspace uint16 color_type; if (TIFFGetField(image, TIFFTAG_PHOTOMETRIC, &color_type) == 0) { dbgFile << "Image has an undefined photometric interpretation"; color_type = PHOTOMETRIC_MINISWHITE; } uint8 dstDepth = 0; QPair colorSpaceIdTag = getColorSpaceForColorType(sampletype, color_type, depth, image, nbchannels, extrasamplescount, dstDepth); if (colorSpaceIdTag.first.isEmpty()) { dbgFile << "Image has an unsupported colorspace :" << color_type << " for this depth :" << depth; TIFFClose(image); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } dbgFile << "Colorspace is :" << colorSpaceIdTag.first << colorSpaceIdTag.second << " with a depth of" << depth << " and with a nb of channels of" << nbchannels; // Read image profile dbgFile << "Reading profile"; const KoColorProfile* profile = 0; quint32 EmbedLen; quint8* EmbedBuffer; if (TIFFGetField(image, TIFFTAG_ICCPROFILE, &EmbedLen, &EmbedBuffer) == 1) { dbgFile << "Profile found"; QByteArray rawdata; rawdata.resize(EmbedLen); memcpy(rawdata.data(), EmbedBuffer, EmbedLen); profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceIdTag.first, colorSpaceIdTag.second, rawdata); } const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorSpaceIdTag.first, colorSpaceIdTag.second); // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { dbgFile << "The profile " << profile->name() << " is not compatible with the color space model " << colorSpaceIdTag.first << " " << colorSpaceIdTag.second; profile = 0; } // Do not use the linear gamma profile for 16 bits/channel by default, tiff files are usually created with // gamma correction. XXX: Should we ask the user? if (!profile) { if (colorSpaceIdTag.first == RGBAColorModelID.id()) { profile = KoColorSpaceRegistry::instance()->profileByName("sRGB-elle-V2-srgbtrc.icc"); } else if (colorSpaceIdTag.first == GrayAColorModelID.id()) { profile = KoColorSpaceRegistry::instance()->profileByName("Gray-D50-elle-V2-srgbtrc.icc"); } } // Retrieve a pointer to the colorspace const KoColorSpace* cs = 0; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile:" << profile -> name() << ""; cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, profile); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, 0); } if (cs == 0) { dbgFile << "Colorspace" << colorSpaceIdTag.first << colorSpaceIdTag.second << " is not available, please check your installation."; TIFFClose(image); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { dbgFile << "The profile can't be used in krita, need conversion"; transform = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Check if there is an alpha channel int8 alphapos = -1; // <- no alpha // Check which extra is alpha if any dbgFile << "There are" << nbchannels << " channels and" << extrasamplescount << " extra channels"; if (sampleinfo) { // index images don't have any sampleinfo, and therefore sampleinfo == 0 for (int i = 0; i < extrasamplescount; i ++) { dbgFile << i << "" << extrasamplescount << "" << (cs->colorChannelCount()) << nbchannels << "" << sampleinfo[i]; if (sampleinfo[i] == EXTRASAMPLE_ASSOCALPHA) { // XXX: dangelo: the color values are already multiplied with // the alpha value. This needs to be reversed later (postprocessor?) alphapos = i; } if (sampleinfo[i] == EXTRASAMPLE_UNASSALPHA) { // color values are not premultiplied with alpha, and can be used as they are. alphapos = i; } } } dbgFile << "Alpha pos:" << alphapos; // Read META Information KoDocumentInfo * info = m_doc->documentInfo(); char* text; if (TIFFGetField(image, TIFFTAG_ARTIST, &text) == 1) { info->setAuthorInfo("creator", text); } if (TIFFGetField(image, TIFFTAG_DOCUMENTNAME, &text) == 1) { info->setAboutInfo("title", text); } if (TIFFGetField(image, TIFFTAG_IMAGEDESCRIPTION, &text) == 1) { info->setAboutInfo("description", text); } // Get the planar configuration uint16 planarconfig; if (TIFFGetField(image, TIFFTAG_PLANARCONFIG, &planarconfig) == 0) { dbgFile << "Plannar configuration is not define"; TIFFClose(image); return KisImageBuilder_RESULT_INVALID_ARG; } // Creating the KisImageSP if (! m_image) { m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, "built image"); m_image->setResolution( POINT_TO_INCH(xres), POINT_TO_INCH(yres )); // It is the "invert" macro because we convert from pointer-per-inchs to points Q_CHECK_PTR(m_image); } else { if (m_image->width() < (qint32)width || m_image->height() < (qint32)height) { quint32 newwidth = (m_image->width() < (qint32)width) ? width : m_image->width(); quint32 newheight = (m_image->height() < (qint32)height) ? height : m_image->height(); m_image->resizeImage(QRect(0,0,newwidth, newheight)); } } KisPaintLayer* layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), quint8_MAX); tdata_t buf = 0; tdata_t* ps_buf = 0; // used only for planar configuration separated KisBufferStreamBase* tiffstream; KisTIFFReaderBase* tiffReader = 0; quint8 poses[5]; KisTIFFPostProcessor* postprocessor = 0; // Configure poses uint8 nbcolorsamples = nbchannels - extrasamplescount; switch (color_type) { case PHOTOMETRIC_MINISWHITE: { poses[0] = 0; poses[1] = 1; postprocessor = new KisTIFFPostProcessorInvert(nbcolorsamples); } break; case PHOTOMETRIC_MINISBLACK: { poses[0] = 0; poses[1] = 1; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_CIELAB: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; postprocessor = new KisTIFFPostProcessorCIELABtoICCLAB(nbcolorsamples); } break; case PHOTOMETRIC_ICCLAB: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_RGB: { if (sampletype == SAMPLEFORMAT_IEEEFP) { poses[2] = 2; poses[1] = 1; poses[0] = 0; poses[3] = 3; } else { poses[0] = 2; poses[1] = 1; poses[2] = 0; poses[3] = 3; } postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_SEPARATED: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; poses[4] = 4; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; default: break; } // Initisalize tiffReader uint16 * lineSizeCoeffs = new uint16[nbchannels]; uint16 vsubsampling = 1; uint16 hsubsampling = 1; for (uint i = 0; i < nbchannels; i++) { lineSizeCoeffs[i] = 1; } if (color_type == PHOTOMETRIC_PALETTE) { uint16 *red; // No need to free them they are free by libtiff uint16 *green; uint16 *blue; if ((TIFFGetField(image, TIFFTAG_COLORMAP, &red, &green, &blue)) == 0) { dbgFile << "Indexed image does not define a palette"; TIFFClose(image); delete [] lineSizeCoeffs; return KisImageBuilder_RESULT_INVALID_ARG; } tiffReader = new KisTIFFReaderFromPalette(layer->paintDevice(), red, green, blue, poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor); } else if (color_type == PHOTOMETRIC_YCBCR) { TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRSUBSAMPLING, &hsubsampling, &vsubsampling); lineSizeCoeffs[1] = hsubsampling; lineSizeCoeffs[2] = hsubsampling; uint16 position; TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRPOSITIONING, &position); if (dstDepth == 8) { tiffReader = new KisTIFFYCbCrReaderTarget8Bit(layer->paintDevice(), layer->image()->width(), layer->image()->height(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, hsubsampling, vsubsampling); } else if (dstDepth == 16) { tiffReader = new KisTIFFYCbCrReaderTarget16Bit(layer->paintDevice(), layer->image()->width(), layer->image()->height(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, hsubsampling, vsubsampling); } } else if (dstDepth == 8) { tiffReader = new KisTIFFReaderTarget8bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor); } else if (dstDepth == 16) { uint16 alphaValue; if (sampletype == SAMPLEFORMAT_IEEEFP) { alphaValue = 15360; // representation of 1.0 in half } else { alphaValue = quint16_MAX; } tiffReader = new KisTIFFReaderTarget16bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, alphaValue); } else if (dstDepth == 32) { union { float f; uint32 i; } alphaValue; if (sampletype == SAMPLEFORMAT_IEEEFP) { alphaValue.f = 1.0f; } else { alphaValue.i = quint32_MAX; } tiffReader = new KisTIFFReaderTarget32bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, alphaValue.i); } if (!tiffReader) { delete postprocessor; delete[] lineSizeCoeffs; TIFFClose(image); dbgFile << "Image has an invalid/unsupported color type: " << color_type; return KisImageBuilder_RESULT_INVALID_ARG; } if (TIFFIsTiled(image)) { dbgFile << "tiled image"; uint32 tileWidth, tileHeight; uint32 x, y; TIFFGetField(image, TIFFTAG_TILEWIDTH, &tileWidth); TIFFGetField(image, TIFFTAG_TILELENGTH, &tileHeight); uint32 linewidth = (tileWidth * depth * nbchannels) / 8; if (planarconfig == PLANARCONFIG_CONTIG) { buf = _TIFFmalloc(TIFFTileSize(image)); if (depth < 16) { tiffstream = new KisBufferStreamContigBelow16((uint8*)buf, depth, linewidth); } else if (depth < 32) { tiffstream = new KisBufferStreamContigBelow32((uint8*)buf, depth, linewidth); } else { tiffstream = new KisBufferStreamContigAbove32((uint8*)buf, depth, linewidth); } } else { ps_buf = new tdata_t[nbchannels]; uint32 * lineSizes = new uint32[nbchannels]; tmsize_t baseSize = TIFFTileSize(image) / nbchannels; for (uint i = 0; i < nbchannels; i++) { ps_buf[i] = _TIFFmalloc(baseSize); lineSizes[i] = tileWidth; // baseSize / lineSizeCoeffs[i]; } tiffstream = new KisBufferStreamSeperate((uint8**) ps_buf, nbchannels, depth, lineSizes); delete [] lineSizes; } dbgFile << linewidth << "" << nbchannels << "" << layer->paintDevice()->colorSpace()->colorChannelCount(); for (y = 0; y < height; y += tileHeight) { for (x = 0; x < width; x += tileWidth) { dbgFile << "Reading tile x =" << x << " y =" << y; if (planarconfig == PLANARCONFIG_CONTIG) { TIFFReadTile(image, buf, x, y, 0, (tsample_t) - 1); } else { for (uint i = 0; i < nbchannels; i++) { TIFFReadTile(image, ps_buf[i], x, y, 0, i); } } uint32 realTileWidth = (x + tileWidth) < width ? tileWidth : width - x; for (uint yintile = 0; y + yintile < height && yintile < tileHeight / vsubsampling;) { tiffReader->copyDataToChannels(x, y + yintile , realTileWidth, tiffstream); yintile += 1; tiffstream->moveToLine(yintile); } tiffstream->restart(); } } } else { dbgFile << "striped image"; tsize_t stripsize = TIFFStripSize(image); uint32 rowsPerStrip; TIFFGetFieldDefaulted(image, TIFFTAG_ROWSPERSTRIP, &rowsPerStrip); dbgFile << rowsPerStrip << "" << height; rowsPerStrip = qMin(rowsPerStrip, height); // when TIFFNumberOfStrips(image) == 1 it might happen that rowsPerStrip is incorrectly set if (planarconfig == PLANARCONFIG_CONTIG) { buf = _TIFFmalloc(stripsize); if (depth < 16) { tiffstream = new KisBufferStreamContigBelow16((uint8*)buf, depth, stripsize / rowsPerStrip); } else if (depth < 32) { tiffstream = new KisBufferStreamContigBelow32((uint8*)buf, depth, stripsize / rowsPerStrip); } else { tiffstream = new KisBufferStreamContigAbove32((uint8*)buf, depth, stripsize / rowsPerStrip); } } else { ps_buf = new tdata_t[nbchannels]; uint32 scanLineSize = stripsize / rowsPerStrip; dbgFile << " scanLineSize for each plan =" << scanLineSize; uint32 * lineSizes = new uint32[nbchannels]; for (uint i = 0; i < nbchannels; i++) { ps_buf[i] = _TIFFmalloc(stripsize); lineSizes[i] = scanLineSize / lineSizeCoeffs[i]; } tiffstream = new KisBufferStreamSeperate((uint8**) ps_buf, nbchannels, depth, lineSizes); delete [] lineSizes; } dbgFile << "Scanline size =" << TIFFRasterScanlineSize(image) << " / strip size =" << TIFFStripSize(image) << " / rowsPerStrip =" << rowsPerStrip << " stripsize/rowsPerStrip =" << stripsize / rowsPerStrip; uint32 y = 0; dbgFile << " NbOfStrips =" << TIFFNumberOfStrips(image) << " rowsPerStrip =" << rowsPerStrip << " stripsize =" << stripsize; for (uint32 strip = 0; y < height; strip++) { if (planarconfig == PLANARCONFIG_CONTIG) { TIFFReadEncodedStrip(image, TIFFComputeStrip(image, y, 0) , buf, (tsize_t) - 1); } else { for (uint i = 0; i < nbchannels; i++) { TIFFReadEncodedStrip(image, TIFFComputeStrip(image, y, i), ps_buf[i], (tsize_t) - 1); } } for (uint32 yinstrip = 0 ; yinstrip < rowsPerStrip && y < height ;) { uint linesread = tiffReader->copyDataToChannels(0, y, width, tiffstream); y += linesread; yinstrip += linesread; tiffstream->moveToLine(yinstrip); } tiffstream->restart(); } } tiffReader->finalize(); delete[] lineSizeCoeffs; delete tiffReader; delete tiffstream; if (planarconfig == PLANARCONFIG_CONTIG) { _TIFFfree(buf); } else { for (uint i = 0; i < nbchannels; i++) { _TIFFfree(ps_buf[i]); } delete[] ps_buf; } m_image->addNode(KisNodeSP(layer), m_image->rootLayer().data()); return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result KisTIFFConverter::buildImage(const QString &filename) { return decode(filename); } KisImageSP KisTIFFConverter::image() { return m_image; } KisImageBuilder_Result KisTIFFConverter::buildFile(const QString &filename, KisImageSP kisimage, KisTIFFOptions options) { dbgFile << "Start writing TIFF File"; if (!kisimage) return KisImageBuilder_RESULT_EMPTY; // Open file for writing TIFF *image; if ((image = TIFFOpen(QFile::encodeName(filename), "w")) == 0) { dbgFile << "Could not open the file for writing" << filename; TIFFClose(image); return (KisImageBuilder_RESULT_FAILURE); } // Set the document information KoDocumentInfo * info = m_doc->documentInfo(); QString title = info->aboutInfo("title"); if (!title.isEmpty()) { TIFFSetField(image, TIFFTAG_DOCUMENTNAME, title.toLatin1().constData()); } QString abstract = info->aboutInfo("description"); if (!abstract.isEmpty()) { TIFFSetField(image, TIFFTAG_IMAGEDESCRIPTION, abstract.toLatin1().constData()); } QString author = info->authorInfo("creator"); if (!author.isEmpty()) { TIFFSetField(image, TIFFTAG_ARTIST, author.toLatin1().constData()); } dbgFile << "xres: " << INCH_TO_POINT(kisimage->xRes()) << " yres: " << INCH_TO_POINT(kisimage->yRes()); TIFFSetField(image, TIFFTAG_XRESOLUTION, INCH_TO_POINT(kisimage->xRes())); // It is the "invert" macro because we convert from pointer-per-inchs to points TIFFSetField(image, TIFFTAG_YRESOLUTION, INCH_TO_POINT(kisimage->yRes())); KisGroupLayer* root = dynamic_cast(kisimage->rootLayer().data()); if (root == 0) { TIFFClose(image); return KisImageBuilder_RESULT_FAILURE; } + KisTIFFWriterVisitor* visitor = new KisTIFFWriterVisitor(image, &options); if (!visitor->visit(root)) { TIFFClose(image); return KisImageBuilder_RESULT_FAILURE; } TIFFClose(image); return KisImageBuilder_RESULT_OK; } void KisTIFFConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/tiff/kis_tiff_converter.h b/plugins/impex/tiff/kis_tiff_converter.h index 0373c5ea94..47298c85c3 100644 --- a/plugins/impex/tiff/kis_tiff_converter.h +++ b/plugins/impex/tiff/kis_tiff_converter.h @@ -1,72 +1,71 @@ /* * Copyright (c) 2005-2006 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_TIFF_CONVERTER_H_ #define _KIS_TIFF_CONVERTER_H_ #include #include #include #include "kis_types.h" #include "kis_global.h" #include "kis_annotation.h" #include class KisDocument; struct KisTIFFOptions { quint16 compressionType = 0; quint16 predictor = 1; bool alpha = true; bool flatten = true; quint16 jpegQuality = 80; quint16 deflateCompress = 6; - quint16 faxMode = 1; quint16 pixarLogCompress = 6; bool saveProfile = true; KisPropertiesConfigurationSP toProperties() const; void fromProperties(KisPropertiesConfigurationSP cfg); }; class KisTIFFConverter : public QObject { Q_OBJECT public: KisTIFFConverter(KisDocument *doc); ~KisTIFFConverter() override; public: KisImageBuilder_Result buildImage(const QString &filename); KisImageBuilder_Result buildFile(const QString &filename, KisImageSP layer, KisTIFFOptions); /** Retrieve the constructed image */ KisImageSP image(); public Q_SLOTS: virtual void cancel(); private: KisImageBuilder_Result decode(const QString &filename); KisImageBuilder_Result readTIFFDirectory(TIFF* image); private: KisImageSP m_image; KisDocument *m_doc; bool m_stop; }; #endif diff --git a/plugins/impex/tiff/kis_tiff_writer_visitor.cpp b/plugins/impex/tiff/kis_tiff_writer_visitor.cpp index c8ef4af556..a2db8503e0 100644 --- a/plugins/impex/tiff/kis_tiff_writer_visitor.cpp +++ b/plugins/impex/tiff/kis_tiff_writer_visitor.cpp @@ -1,225 +1,224 @@ /* * Copyright (c) 2006 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_tiff_writer_visitor.h" #include #include #include #include #ifdef HAVE_OPENEXR #include #endif namespace { bool writeColorSpaceInformation(TIFF* image, const KoColorSpace * cs, uint16& color_type, uint16& sample_format) { dbgKrita << cs->id(); if (cs->id() == "GRAYA" || cs->id() == "GRAYAU16") { color_type = PHOTOMETRIC_MINISBLACK; return true; } if (KoID(cs->id()) == KoID("RGBA") || KoID(cs->id()) == KoID("RGBA16")) { color_type = PHOTOMETRIC_RGB; return true; } if (KoID(cs->id()) == KoID("RGBAF16") || KoID(cs->id()) == KoID("RGBAF32")) { color_type = PHOTOMETRIC_RGB; sample_format = SAMPLEFORMAT_IEEEFP; return true; } if (cs->id() == "CMYK" || cs->id() == "CMYKAU16") { color_type = PHOTOMETRIC_SEPARATED; TIFFSetField(image, TIFFTAG_INKSET, INKSET_CMYK); return true; } if (cs->id() == "LABA") { color_type = PHOTOMETRIC_ICCLAB; return true; } return false; } } KisTIFFWriterVisitor::KisTIFFWriterVisitor(TIFF*image, KisTIFFOptions* options) : m_image(image) , m_options(options) { } KisTIFFWriterVisitor::~KisTIFFWriterVisitor() { } bool KisTIFFWriterVisitor::copyDataToStrips(KisHLineConstIteratorSP it, tdata_t buff, uint8 depth, uint16 sample_format, uint8 nbcolorssamples, quint8* poses) { if (depth == 32) { Q_ASSERT(sample_format == SAMPLEFORMAT_IEEEFP); float *dst = reinterpret_cast(buff); do { const float *d = reinterpret_cast(it->oldRawData()); int i; for (i = 0; i < nbcolorssamples; i++) { *(dst++) = d[poses[i]]; } if (m_options->alpha) *(dst++) = d[poses[i]]; } while (it->nextPixel()); return true; } else if (depth == 16 ) { if (sample_format == SAMPLEFORMAT_IEEEFP) { #ifdef HAVE_OPENEXR half *dst = reinterpret_cast(buff); do { const half *d = reinterpret_cast(it->oldRawData()); int i; for (i = 0; i < nbcolorssamples; i++) { *(dst++) = d[poses[i]]; } if (m_options->alpha) *(dst++) = d[poses[i]]; } while (it->nextPixel()); return true; #endif } else { quint16 *dst = reinterpret_cast(buff); do { const quint16 *d = reinterpret_cast(it->oldRawData()); int i; for (i = 0; i < nbcolorssamples; i++) { *(dst++) = d[poses[i]]; } if (m_options->alpha) *(dst++) = d[poses[i]]; } while (it->nextPixel()); return true; } } else if (depth == 8) { quint8 *dst = reinterpret_cast(buff); do { const quint8 *d = it->oldRawData(); int i; for (i = 0; i < nbcolorssamples; i++) { *(dst++) = d[poses[i]]; } if (m_options->alpha) *(dst++) = d[poses[i]]; } while (it->nextPixel()); return true; } return false; } bool KisTIFFWriterVisitor::saveLayerProjection(KisLayer *layer) { dbgFile << "visiting on layer" << layer->name() << ""; KisPaintDeviceSP pd = layer->projection(); // Save depth int depth = 8 * pd->pixelSize() / pd->channelCount(); TIFFSetField(image(), TIFFTAG_BITSPERSAMPLE, depth); // Save number of samples if (m_options->alpha) { TIFFSetField(image(), TIFFTAG_SAMPLESPERPIXEL, pd->channelCount()); uint16 sampleinfo[1] = { EXTRASAMPLE_UNASSALPHA }; TIFFSetField(image(), TIFFTAG_EXTRASAMPLES, 1, sampleinfo); } else { TIFFSetField(image(), TIFFTAG_SAMPLESPERPIXEL, pd->channelCount() - 1); TIFFSetField(image(), TIFFTAG_EXTRASAMPLES, 0); } // Save colorspace information uint16 color_type; uint16 sample_format = SAMPLEFORMAT_UINT; if (!writeColorSpaceInformation(image(), pd->colorSpace(), color_type, sample_format)) { // unsupported colorspace return false; } TIFFSetField(image(), TIFFTAG_PHOTOMETRIC, color_type); TIFFSetField(image(), TIFFTAG_SAMPLEFORMAT, sample_format); TIFFSetField(image(), TIFFTAG_IMAGEWIDTH, layer->image()->width()); TIFFSetField(image(), TIFFTAG_IMAGELENGTH, layer->image()->height()); // Set the compression options TIFFSetField(image(), TIFFTAG_COMPRESSION, m_options->compressionType); - TIFFSetField(image(), TIFFTAG_FAXMODE, m_options->faxMode); TIFFSetField(image(), TIFFTAG_JPEGQUALITY, m_options->jpegQuality); TIFFSetField(image(), TIFFTAG_ZIPQUALITY, m_options->deflateCompress); TIFFSetField(image(), TIFFTAG_PIXARLOGQUALITY, m_options->pixarLogCompress); // Set the predictor TIFFSetField(image(), TIFFTAG_PREDICTOR, m_options->predictor); // Use contiguous configuration TIFFSetField(image(), TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); // Use 8 rows per strip TIFFSetField(image(), TIFFTAG_ROWSPERSTRIP, 8); // Save profile if (m_options->saveProfile) { const KoColorProfile* profile = pd->colorSpace()->profile(); if (profile && profile->type() == "icc" && !profile->rawData().isEmpty()) { QByteArray ba = profile->rawData(); TIFFSetField(image(), TIFFTAG_ICCPROFILE, ba.size(), ba.constData()); } } tsize_t stripsize = TIFFStripSize(image()); tdata_t buff = _TIFFmalloc(stripsize); qint32 height = layer->image()->height(); qint32 width = layer->image()->width(); bool r = true; for (int y = 0; y < height; y++) { KisHLineConstIteratorSP it = pd->createHLineConstIteratorNG(0, y, width); switch (color_type) { case PHOTOMETRIC_MINISBLACK: { quint8 poses[] = { 0, 1 }; r = copyDataToStrips(it, buff, depth, sample_format, 1, poses); } break; case PHOTOMETRIC_RGB: { quint8 poses[4]; if (sample_format == SAMPLEFORMAT_IEEEFP) { poses[2] = 2; poses[1] = 1; poses[0] = 0; poses[3] = 3; } else { poses[0] = 2; poses[1] = 1; poses[2] = 0; poses[3] = 3; } r = copyDataToStrips(it, buff, depth, sample_format, 3, poses); } break; case PHOTOMETRIC_SEPARATED: { quint8 poses[] = { 0, 1, 2, 3, 4 }; r = copyDataToStrips(it, buff, depth, sample_format, 4, poses); } break; case PHOTOMETRIC_ICCLAB: { quint8 poses[] = { 0, 1, 2, 3 }; r = copyDataToStrips(it, buff, depth, sample_format, 3, poses); } break; return false; } if (!r) return false; TIFFWriteScanline(image(), buff, y, (tsample_t) - 1); } _TIFFfree(buff); TIFFWriteDirectory(image()); return true; } diff --git a/plugins/impex/tiff/kis_wdg_options_tiff.ui b/plugins/impex/tiff/kis_wdg_options_tiff.ui index c4c51434be..9f9fee53dc 100644 --- a/plugins/impex/tiff/kis_wdg_options_tiff.ui +++ b/plugins/impex/tiff/kis_wdg_options_tiff.ui @@ -1,639 +1,612 @@ KisWdgOptionsTIFF 0 0 411 - 276 + 299 0 0 TIFF Options - + + 0 + + + 0 + + + 0 + + 0 0 0 TIFF Options Compression type: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false None JPEG DCT Compression Deflate (ZIP) Lempel-Ziv & Welch (LZW) - - - Leadtools JPEG2000 - - - - - CCITT Modified Huffman RLE - - - - - CCITT Group 3 Fax Encoding - - - - - CCITT Group 4 Fax Encoding - - Pixar Log Predictor: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false Using a predictor can improve the compression (mostly for LZW and deflate.) 0 None Horizontal Differencing Floating Point Horizontal Differencing Disable to get smaller files if your image has no transparency <p>The Portable Network Graphics (PNG) file format allows transparency in your image to be stored by saving an alpha channel. You can uncheck the box if you are not using transparency and you want to make the resulting file smaller .<br>Always saving the alpha channel is recommended.</p> Store alpha &channel (transparency) true 0 0 This option will merge all layers. It is advisable to check this option, otherwise other applications might not be able to read your file correctly. Flatten the &image true Save ICC Profile true QFrame::NoFrame + + 0 + 0 - + + 0 + + + 0 + + + 0 + + 0 QFrame::NoFrame QFrame::Plain 0 - + + 0 + + + 0 + + + 0 + + 0 0 0 JPEG Compression Options Quality: Qt::AlignTop false These settings determine how much information is lost during compression 0 100 1 1 80 Qt::Horizontal QSlider::TicksBothSides 10 Smallest false Best Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false 0 - + + 0 + + + 0 + + + 0 + + 0 0 0 Deflate Compression Options Note: the compression level does not change the quality of the result <p>Adjust the compression time. Better compression takes longer. <br>Note: the compression level does not change the quality of the result.</p> Compress: Qt::AlignTop false Note: the compression level does not change the quality of the result <p>Adjust the compression time. Better compression takes longer. <br>Note: the compression level does not change the quality of the result.</p> 1 9 1 6 Qt::Horizontal QSlider::TicksBothSides <p>Adjust the compression time. Better compression takes longer. <br>Note: the compression level does not change the quality of the result.</p> Fast false <p>Adjust the compression time. Better compression takes longer. <br>Note: the compression level does not change the quality of the result.</p> Small Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false - + 0 - + 0 - - - - - 0 - 0 - - - - CCITT Group 3 fax encoding Options - - - - - - Fax mode: - - - false - - - - - - - - Classic - - - - - No RTC - - - - - No EOL - - - - - - - - - - - - + + 0 + + 0 - + 0 0 0 Pixar Log Compression Options Note: the compression level does not change the quality of the result <p>Adjust the compression time. Better compression takes longer. <br>Note: the compression level does not change the quality of the result.</p> Compress: Qt::AlignTop false Note: the compression level does not change the quality of the result <p>Adjust the compression time. Better compression takes longer. <br>Note: the compression level does not change the quality of the result.</p> 1 9 1 6 Qt::Horizontal QSlider::TicksBothSides <p>Adjust the compression time. Better compression takes longer. <br>Note: the compression level does not change the quality of the result.</p> Fast false <p>Adjust the compression time. Better compression takes longer. <br>Note: the compression level does not change the quality of the result.</p> Small Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false Qt::Vertical QSizePolicy::Expanding 20 16 KComboBox QComboBox
    kcombobox.h
    kComboBoxCompressionType kComboBoxPredictor alpha flatten qualityLevel compressionLevelDeflate - kComboBoxFaxMode compressionLevelPixarLog
    diff --git a/plugins/tools/basictools/kis_tool_move.cc b/plugins/tools/basictools/kis_tool_move.cc index 1472fc8ef2..c1d3a81554 100644 --- a/plugins/tools/basictools/kis_tool_move.cc +++ b/plugins/tools/basictools/kis_tool_move.cc @@ -1,571 +1,595 @@ /* * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 2002 Patrick Julien * 2004 Boudewijn Rempt * 2016 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 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_move.h" #include #include "kis_cursor.h" #include "kis_selection.h" #include "kis_canvas2.h" #include "kis_image.h" #include "kis_tool_utils.h" #include "kis_paint_layer.h" #include "strokes/move_stroke_strategy.h" #include "kis_tool_movetooloptionswidget.h" #include "strokes/move_selection_stroke_strategy.h" #include "kis_resources_snapshot.h" #include "kis_action_registry.h" #include "krita_utils.h" #include #include #include "kis_node_manager.h" #include "kis_signals_blocker.h" +#include + +struct KisToolMoveState : KisToolChangesTrackerData, boost::equality_comparable +{ + KisToolMoveState(QPoint _accumulatedOffset) : accumulatedOffset(_accumulatedOffset) {} + KisToolChangesTrackerData* clone() const { return new KisToolMoveState(*this); } + + bool operator ==(const KisToolMoveState &rhs) { + return accumulatedOffset == rhs.accumulatedOffset; + } + + QPoint accumulatedOffset; +}; + KisToolMove::KisToolMove(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::moveCursor()) { m_canvas = dynamic_cast(canvas); setObjectName("tool_move"); m_optionsWidget = 0; - m_moveInProgress = false; QAction *a; KisActionRegistry *actionRegistry = KisActionRegistry::instance(); a = actionRegistry->makeQAction("movetool-move-up", this); addAction("movetool-move-up", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Up, false);}); a = actionRegistry->makeQAction("movetool-move-down", this); addAction("movetool-move-down", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Down, false);}); a = actionRegistry->makeQAction("movetool-move-left", this); addAction("movetool-move-left", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Left, false);}); a = actionRegistry->makeQAction("movetool-move-right", this); addAction("movetool-move-right", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Right, false);}); a = actionRegistry->makeQAction("movetool-move-up-more", this); addAction("movetool-move-up-more", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Up, true);}); a = actionRegistry->makeQAction("movetool-move-down-more", this); addAction("movetool-move-down-more", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Down, true);}); a = actionRegistry->makeQAction("movetool-move-left-more", this); addAction("movetool-move-left-more", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Left, true);}); a = actionRegistry->makeQAction("movetool-move-right-more", this); addAction("movetool-move-right-more", a); connect(a, &QAction::triggered, [&](){moveDiscrete(MoveDirection::Right, true);}); m_showCoordinatesAction = actionRegistry->makeQAction("movetool-show-coordinates", this); addAction("movetool-show-coordinates", m_showCoordinatesAction); + + connect(&m_changesTracker, + SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)), + SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP))); } KisToolMove::~KisToolMove() { endStroke(); } void KisToolMove::resetCursorStyle() { KisTool::resetCursorStyle(); overrideCursorIfNotEditable(); } bool KisToolMove::startStrokeImpl(MoveToolMode mode, const QPoint *pos) { if (!currentNode()->isEditable()) return false; KisNodeSP node; KisNodeList nodes; KisImageSP image = this->image(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, currentNode(), this->canvas()->resourceManager()); KisSelectionSP selection = resources->activeSelection(); if (mode != MoveSelectedLayer && pos) { bool wholeGroup = !selection && mode == MoveGroup; node = KisToolUtils::findNode(image->root(), *pos, wholeGroup); if (node) { nodes = {node}; } } if (nodes.isEmpty()) { nodes = this->selectedNodes(); KritaUtils::filterContainer(nodes, [](KisNodeSP node) { return node->isEditable(); }); } if (nodes.size() == 1) { node = nodes.first(); } if (nodes.isEmpty()) { return false; } - initHandles(nodes); - /** * If the target node has changed, the stroke should be * restarted. Otherwise just continue processing current node. */ - if (m_strokeId) { - if (KritaUtils::compareListsUnordered(nodes, m_currentlyProcessingNodes)) { - return true; - } else { - endStroke(); - } + if (m_strokeId && !tryEndPreviousStroke(nodes)) { + return true; } + initHandles(nodes); + KisStrokeStrategy *strategy; KisPaintLayerSP paintLayer = node ? dynamic_cast(node.data()) : 0; if (paintLayer && selection && !selection->isTotallyUnselected(image->bounds())) { strategy = new MoveSelectionStrokeStrategy(paintLayer, selection, image.data(), image.data()); } else { strategy = new MoveStrokeStrategy(nodes, image.data(), image.data()); } m_strokeId = image->startStroke(strategy); m_currentlyProcessingNodes = nodes; m_accumulatedOffset = QPoint(); + KIS_SAFE_ASSERT_RECOVER(m_changesTracker.isEmpty()) { + m_changesTracker.reset(); + } + commitChanges(); + return true; } -void KisToolMove::moveDiscrete(MoveDirection direction, bool big) +QPoint KisToolMove::currentOffset() const { - if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging - if (!currentNode()->isEditable()) return; // Don't move invisible nodes + return m_accumulatedOffset + m_dragPos - m_dragStart; +} - if (startStrokeImpl(MoveSelectedLayer, 0)) { - setMode(KisTool::PAINT_MODE); - } +void KisToolMove::notifyGuiAfterMove(bool showFloatingMessage) +{ + if (!m_optionsWidget) return; - // Larger movement if "shift" key is pressed. - qreal scale = big ? m_optionsWidget->moveScale() : 1.0; - qreal moveStep = m_optionsWidget->moveStep() * scale; + const QPoint currentTopLeft = m_handlesRect.topLeft() + currentOffset(); - QPoint offset = direction == Up ? QPoint( 0, -moveStep) : - direction == Down ? QPoint( 0, moveStep) : - direction == Left ? QPoint(-moveStep, 0) : - QPoint( moveStep, 0) ; + KisSignalsBlocker b(m_optionsWidget); + emit moveInNewPosition(currentTopLeft); + // TODO: fetch this info not from options widget, but from config const bool showCoordinates = m_optionsWidget->showCoordinates(); - if (showCoordinates) { + if (showCoordinates && showFloatingMessage) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in move tool", "X: %1 px, Y: %2 px", - (m_startPosition + offset).x(), - (m_startPosition + offset).y()), + currentTopLeft.x(), + currentTopLeft.y()), QIcon(), 1000, KisFloatingMessage::High); } +} - KisSignalsBlocker b(m_optionsWidget); - emit moveInNewPosition(m_startPosition + offset); +bool KisToolMove::tryEndPreviousStroke(KisNodeList nodes) +{ + if (!m_strokeId) return false; + + bool strokeEnded = false; + + if (!KritaUtils::compareListsUnordered(nodes, m_currentlyProcessingNodes)) { + endStroke(); + strokeEnded = true; + } + + return strokeEnded; +} + +void KisToolMove::commitChanges() +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(m_strokeId); + + QSharedPointer newState(new KisToolMoveState(m_accumulatedOffset)); + KisToolMoveState *lastState = dynamic_cast(m_changesTracker.lastState().data()); + if (lastState && *lastState == *newState) return; + + m_changesTracker.commitConfig(newState); +} + +void KisToolMove::moveDiscrete(MoveDirection direction, bool big) +{ + if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging + if (!currentNode()->isEditable()) return; // Don't move invisible nodes + + if (startStrokeImpl(MoveSelectedLayer, 0)) { + setMode(KisTool::PAINT_MODE); + } + + // Larger movement if "shift" key is pressed. + qreal scale = big ? m_optionsWidget->moveScale() : 1.0; + qreal moveStep = m_optionsWidget->moveStep() * scale; - m_startPosition += offset; + const QPoint offset = + direction == Up ? QPoint( 0, -moveStep) : + direction == Down ? QPoint( 0, moveStep) : + direction == Left ? QPoint(-moveStep, 0) : + QPoint( moveStep, 0) ; - image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset + offset)); m_accumulatedOffset += offset; + image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); - m_moveInProgress = false; - emit moveInProgressChanged(); + notifyGuiAfterMove(); + commitChanges(); setMode(KisTool::HOVER_MODE); } void KisToolMove::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); QRect totalBounds; slotNodeChanged(this->selectedNodes()); } void KisToolMove::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if (m_dragInProgress) { QPainterPath handles; - handles.addRect(m_handlesRect.translated(m_pos)); + handles.addRect(m_handlesRect.translated(currentOffset())); QPainterPath path = pixelToView(handles); paintToolOutline(&gc, path); } } void KisToolMove::initHandles(const KisNodeList &nodes) { + /** + * The handles should be initialized only once, **before** the start of + * the stroke. If the nodes change, we should restart the stroke. + */ + KIS_SAFE_ASSERT_RECOVER_NOOP(!m_strokeId); + m_handlesRect = QRect(); for (KisNodeSP node : nodes) { node->exactBounds(); m_handlesRect |= node->exactBounds(); } if (image()->globalSelection()) { m_handlesRect &= image()->globalSelection()->selectedRect(); } - if (m_handlesRect.topLeft() != m_startPosition) { - m_handlesRect.moveTopLeft(m_startPosition); - } } void KisToolMove::deactivate() { endStroke(); KisTool::deactivate(); } void KisToolMove::requestStrokeEnd() { endStroke(); } void KisToolMove::requestStrokeCancellation() { cancelStroke(); } void KisToolMove::requestUndoDuringStroke() { - // we shouldn't cancel the stroke on Ctrl+Z, because it will not only - // cancel the stroke, but also undo the previous command, which we haven't - // yet pushed to the stack + if (!m_strokeId) return; + m_changesTracker.requestUndo(); } void KisToolMove::beginPrimaryAction(KoPointerEvent *event) { startAction(event, moveToolMode()); } void KisToolMove::continuePrimaryAction(KoPointerEvent *event) { continueAction(event); } void KisToolMove::endPrimaryAction(KoPointerEvent *event) { endAction(event); } void KisToolMove::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { // Ctrl+Right click toggles between moving current layer and moving layer w/ content if (action == PickFgNode || action == PickBgImage) { MoveToolMode mode = moveToolMode(); if (mode == MoveSelectedLayer) { mode = MoveFirstLayer; } else if (mode == MoveFirstLayer) { mode = MoveSelectedLayer; } startAction(event, mode); } else { startAction(event, MoveGroup); } } void KisToolMove::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(action) continueAction(event); } void KisToolMove::endAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(action) endAction(event); } void KisToolMove::startAction(KoPointerEvent *event, MoveToolMode mode) { QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); m_dragStart = pos; - m_pos = QPoint(); - m_moveInProgress = true; + m_dragPos = pos; m_dragInProgress = true; - emit moveInProgressChanged(); if (startStrokeImpl(mode, &pos)) { setMode(KisTool::PAINT_MODE); } else { event->ignore(); + m_dragPos = QPoint(); + m_dragStart = QPoint(); + m_dragInProgress = false; } m_canvas->updateCanvas(); } void KisToolMove::continueAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (!m_strokeId) return; QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); - - const bool showCoordinates = - m_optionsWidget ? m_optionsWidget->showCoordinates() : true; - - if (showCoordinates) { - KisCanvas2 *kisCanvas = dynamic_cast(canvas()); - kisCanvas->viewManager()-> - showFloatingMessage( - i18nc("floating message in move tool", - "X: %1 px, Y: %2 px", - (m_startPosition + (pos - m_dragStart)).x(), - (m_startPosition + (pos - m_dragStart)).y()), - QIcon(), 1000, KisFloatingMessage::High); - } - - KisSignalsBlocker b(m_optionsWidget); - emit moveInNewPosition(m_startPosition + (pos - m_dragStart)); - pos = applyModifiers(event->modifiers(), pos); + m_dragPos = pos; + drag(pos); - m_pos = pos - m_dragStart; + notifyGuiAfterMove(); + m_canvas->updateCanvas(); } void KisToolMove::endAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); if (!m_strokeId) return; QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); pos = applyModifiers(event->modifiers(), pos); drag(pos); - m_pos = pos - m_dragStart; + m_accumulatedOffset += m_dragPos - m_dragStart; + m_dragStart = QPoint(); + m_dragPos = QPoint(); m_dragInProgress = false; - m_startPosition += pos - m_dragStart; - m_accumulatedOffset += pos - m_dragStart; + commitChanges(); + + notifyGuiAfterMove(); + m_canvas->updateCanvas(); } void KisToolMove::drag(const QPoint& newPos) { KisImageWSP image = currentImage(); QPoint offset = m_accumulatedOffset + newPos - m_dragStart; image->addJob(m_strokeId, new MoveStrokeStrategy::Data(offset)); } void KisToolMove::endStroke() { if (!m_strokeId) return; KisImageSP image = currentImage(); image->endStroke(m_strokeId); m_strokeId.clear(); + m_changesTracker.reset(); m_currentlyProcessingNodes.clear(); - m_moveInProgress = false; - emit moveInProgressChanged(); + m_accumulatedOffset = QPoint(); +} + +void KisToolMove::slotTrackerChangedConfig(KisToolChangesTrackerDataSP state) +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(m_strokeId); + + KisToolMoveState *newState = dynamic_cast(state.data()); + KIS_SAFE_ASSERT_RECOVER_RETURN(newState); + + if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging + m_accumulatedOffset = newState->accumulatedOffset; + image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); + notifyGuiAfterMove(); } void KisToolMove::cancelStroke() { if (!m_strokeId) return; KisImageSP image = currentImage(); image->cancelStroke(m_strokeId); m_strokeId.clear(); + m_changesTracker.reset(); m_currentlyProcessingNodes.clear(); - m_moveInProgress = false; - emit moveInProgressChanged(); - - // we should reset m_startPosition into the original value when - // the stroke is cancelled - KisCanvas2 *canvas = dynamic_cast(this->canvas()); - canvas->viewManager()->blockUntilOperationsFinishedForced(image); - slotNodeChanged(this->selectedNodes()); + m_accumulatedOffset = QPoint(); + notifyGuiAfterMove(); } QWidget* KisToolMove::createOptionWidget() { if (!currentImage()) return 0; m_optionsWidget = new MoveToolOptionsWidget(0, currentImage()->xRes(), toolId()); // 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); m_optionsWidget->setFixedHeight(m_optionsWidget->sizeHint().height()); connect(m_showCoordinatesAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(setShowCoordinates(bool))); connect(m_optionsWidget, SIGNAL(showCoordinatesChanged(bool)), m_showCoordinatesAction, SLOT(setChecked(bool))); m_showCoordinatesAction->setChecked(m_optionsWidget->showCoordinates()); - m_optionsWidget->slotSetTranslate(m_startPosition); + m_optionsWidget->slotSetTranslate(m_handlesRect.topLeft() + currentOffset()); connect(m_optionsWidget, SIGNAL(sigSetTranslateX(int)), SLOT(moveBySpinX(int))); connect(m_optionsWidget, SIGNAL(sigSetTranslateY(int)), SLOT(moveBySpinY(int))); + connect(m_optionsWidget, SIGNAL(sigRequestCommitOffsetChanges()), this, SLOT(commitChanges())); connect(this, SIGNAL(moveInNewPosition(QPoint)), m_optionsWidget, SLOT(slotSetTranslate(QPoint))); KisCanvas2 *kisCanvas = dynamic_cast(canvas()); connect(kisCanvas->viewManager()->nodeManager(), SIGNAL(sigUiNeedChangeSelectedNodes(KisNodeList)), this, SLOT(slotNodeChanged(KisNodeList))); return m_optionsWidget; } KisToolMove::MoveToolMode KisToolMove::moveToolMode() const { if (m_optionsWidget) return m_optionsWidget->mode(); return MoveSelectedLayer; } -bool KisToolMove::moveInProgress() const -{ - return m_moveInProgress; -} - QPoint KisToolMove::applyModifiers(Qt::KeyboardModifiers modifiers, QPoint pos) { QPoint move = pos - m_dragStart; // Snap to axis if (modifiers & Qt::ShiftModifier) { move = snapToClosestAxis(move); } // "Precision mode" - scale down movement by 1/5 if (modifiers & Qt::AltModifier) { const qreal SCALE_FACTOR = .2; move = SCALE_FACTOR * move; } return m_dragStart + move; } void KisToolMove::moveBySpinX(int newX) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } - int offsetX = newX - m_startPosition.x(); - QPoint offset = QPoint(offsetX, 0); - - KisSignalsBlocker b(m_optionsWidget); - emit moveInNewPosition(m_startPosition + offset); + m_accumulatedOffset.rx() = newX - m_handlesRect.x(); - image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset + offset)); - m_accumulatedOffset += offset; - m_startPosition += offset; + image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); - m_moveInProgress = false; - emit moveInProgressChanged(); + notifyGuiAfterMove(false); setMode(KisTool::HOVER_MODE); } void KisToolMove::moveBySpinY(int newY) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } - int offsetY = newY - m_startPosition.y(); - QPoint offset = QPoint(0, offsetY); - - KisSignalsBlocker b(m_optionsWidget); - emit moveInNewPosition(m_startPosition + offset); + m_accumulatedOffset.ry() = newY - m_handlesRect.y(); - image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset + offset)); - m_accumulatedOffset += offset; - m_startPosition += offset; + image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); - m_moveInProgress = false; - emit moveInProgressChanged(); + notifyGuiAfterMove(false); setMode(KisTool::HOVER_MODE); } void KisToolMove::slotNodeChanged(KisNodeList nodes) { - QRect totalBounds; - - Q_FOREACH (KisNodeSP node, nodes) { - if (node && node->projection()) { - totalBounds |= node->projection()->nonDefaultPixelArea(); - } - } - - if (image()->globalSelection()) { - totalBounds &= image()->globalSelection()->selectedRect(); + if (m_strokeId && !tryEndPreviousStroke(nodes)) { + return; } - m_startPosition = totalBounds.topLeft(); - - if (m_optionsWidget) - { - KisSignalsBlocker b(m_optionsWidget); - m_optionsWidget->slotSetTranslate(m_startPosition); - } + initHandles(nodes); + notifyGuiAfterMove(false); } diff --git a/plugins/tools/basictools/kis_tool_move.h b/plugins/tools/basictools/kis_tool_move.h index d2dfa55727..8ed0649966 100644 --- a/plugins/tools/basictools/kis_tool_move.h +++ b/plugins/tools/basictools/kis_tool_move.h @@ -1,182 +1,186 @@ /* * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 2003 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_TOOL_MOVE_H_ #define KIS_TOOL_MOVE_H_ #include #include #include #include #include #include #include #include #include +#include "KisToolChangesTracker.h" #include "kis_canvas2.h" class KoCanvasBase; class MoveToolOptionsWidget; class KisDocument; class KisToolMove : public KisTool { Q_OBJECT Q_ENUMS(MoveToolMode); - Q_PROPERTY(bool moveInProgress READ moveInProgress NOTIFY moveInProgressChanged); public: KisToolMove(KoCanvasBase * canvas); ~KisToolMove() override; /** * @brief wantsAutoScroll * reimplemented from KoToolBase * there's an issue where autoscrolling with this tool never makes the * stroke end, so we return false here so that users don't get stuck with * the tool. See bug 362659 * @return false */ bool wantsAutoScroll() const override { return false; } public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void deactivate() override; public Q_SLOTS: void requestStrokeEnd() override; void requestStrokeCancellation() override; void requestUndoDuringStroke() override; protected Q_SLOTS: void resetCursorStyle() override; public: enum MoveToolMode { MoveSelectedLayer, MoveFirstLayer, MoveGroup }; enum MoveDirection { Up, Down, Left, Right }; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void beginAlternateAction(KoPointerEvent *event, AlternateAction action) override; void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override; void endAlternateAction(KoPointerEvent *event, AlternateAction action) override; void startAction(KoPointerEvent *event, MoveToolMode mode); void continueAction(KoPointerEvent *event); void endAction(KoPointerEvent *event); void paint(QPainter& gc, const KoViewConverter &converter) override; void initHandles(const KisNodeList &nodes); QWidget* createOptionWidget() override; void updateUIUnit(int newUnit); MoveToolMode moveToolMode() const; - bool moveInProgress() const; void setShowCoordinates(bool value); public Q_SLOTS: void moveDiscrete(MoveDirection direction, bool big); void moveBySpinX(int newX); void moveBySpinY(int newY); void slotNodeChanged(KisNodeList nodes); + void commitChanges(); Q_SIGNALS: void moveToolModeChanged(); - void moveInProgressChanged(); void moveInNewPosition(QPoint); private: void drag(const QPoint& newPos); void cancelStroke(); QPoint applyModifiers(Qt::KeyboardModifiers modifiers, QPoint pos); bool startStrokeImpl(MoveToolMode mode, const QPoint *pos); + QPoint currentOffset() const; + void notifyGuiAfterMove(bool showFloatingMessage = true); + bool tryEndPreviousStroke(KisNodeList nodes); + + private Q_SLOTS: void endStroke(); + void slotTrackerChangedConfig(KisToolChangesTrackerDataSP state); private: MoveToolOptionsWidget* m_optionsWidget; QPoint m_dragStart; ///< Point where current cursor dragging began QPoint m_accumulatedOffset; ///< Total offset including multiple clicks, up/down/left/right keys, etc. added together - QPoint m_startPosition; - KisStrokeId m_strokeId; - bool m_moveInProgress; KisNodeList m_currentlyProcessingNodes; int m_resolution; QAction *m_showCoordinatesAction; KisCanvas2* m_canvas; - QPoint m_pos; + QPoint m_dragPos; QRect m_handlesRect; bool m_dragInProgress = false; + + KisToolChangesTracker m_changesTracker; }; class KisToolMoveFactory : public KoToolFactoryBase { public: KisToolMoveFactory() : KoToolFactoryBase("KritaTransform/KisToolMove") { setToolTip(i18n("Move Tool")); setSection(TOOL_TYPE_TRANSFORM); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setPriority(3); setIconName(koIconNameCStr("krita_tool_move")); setShortcut(QKeySequence( Qt::Key_T)); } ~KisToolMoveFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolMove(canvas); } }; #endif // KIS_TOOL_MOVE_H_ diff --git a/plugins/tools/basictools/kis_tool_movetooloptionswidget.cpp b/plugins/tools/basictools/kis_tool_movetooloptionswidget.cpp index c0c0dddfad..2aa47948ef 100644 --- a/plugins/tools/basictools/kis_tool_movetooloptionswidget.cpp +++ b/plugins/tools/basictools/kis_tool_movetooloptionswidget.cpp @@ -1,186 +1,189 @@ /* Copyright (C) 2012 Dan Leinir Turthra Jensen 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_movetooloptionswidget.h" #include #include MoveToolOptionsWidget::MoveToolOptionsWidget(QWidget *parent, int resolution, QString toolId) : QWidget(parent) , m_resolution(resolution) , m_showCoordinates(false) { setupUi(this); m_configGroup = KSharedConfig::openConfig()->group(toolId); // load radio button m_moveToolMode = static_cast(m_configGroup.readEntry("moveToolMode", 0)); if(m_moveToolMode == KisToolMove::MoveSelectedLayer) radioSelectedLayer->setChecked(true); else if (m_moveToolMode == KisToolMove::MoveFirstLayer) radioFirstLayer->setChecked(true); else radioGroup->setChecked(true); // Keyboard shortcut move step m_moveStep = m_configGroup.readEntry("moveToolStep", 1); m_moveStepUnit = m_configGroup.readEntry("moveToolUnit", KoUnit(KoUnit::Pixel).indexInListForUi()); cmbUnit->addItems(KoUnit::listOfUnitNameForUi()); cmbUnit->setCurrentIndex(m_moveStepUnit); updateUIUnit(m_moveStepUnit); // Scale for large moves m_moveScale = m_configGroup.readEntry("moveToolScale", 10.0); spinMoveScale->blockSignals(true); spinMoveScale->setValue(m_moveScale); spinMoveScale->setSuffix("x"); spinMoveScale->blockSignals(false); // Switch mode for showing coordinates m_showCoordinates = m_configGroup.readEntry("moveToolShowCoordinates", false); connect(chkShowCoordinates, SIGNAL(toggled(bool)), SIGNAL(showCoordinatesChanged(bool))); chkShowCoordinates->setChecked(m_showCoordinates); translateXBox->setSuffix(i18n(" px")); translateYBox->setSuffix(i18n(" px")); translateXBox->setRange(-10000, 10000); translateYBox->setRange(-10000, 10000); } void MoveToolOptionsWidget::updateUIUnit(int newUnit) { const KoUnit selectedUnit = KoUnit::fromListForUi(newUnit); qreal valueForUI; if (selectedUnit != KoUnit(KoUnit::Pixel)) { spinMoveStep->setRange(0.0001, 10000.000); spinMoveStep->setSingleStep(.1); spinMoveStep->setDecimals(4); valueForUI = selectedUnit.toUserValue((qreal)m_moveStep / (qreal)m_resolution); } else { spinMoveStep->setRange(1, 10000); spinMoveStep->setSingleStep(1); spinMoveStep->setDecimals(0); valueForUI = m_moveStep; } spinMoveStep->blockSignals(true); spinMoveStep->setValue(valueForUI); spinMoveStep->blockSignals(false); + + connect(translateXBox, SIGNAL(editingFinished()), SIGNAL(sigRequestCommitOffsetChanges())); + connect(translateYBox, SIGNAL(editingFinished()), SIGNAL(sigRequestCommitOffsetChanges())); } void MoveToolOptionsWidget::on_spinMoveStep_valueChanged(double UIMoveStep) { const KoUnit selectedUnit = KoUnit::fromListForUi(m_moveStepUnit); const double scaledUiMoveStep = (selectedUnit == KoUnit(KoUnit::Pixel)) ? UIMoveStep : selectedUnit.fromUserValue(UIMoveStep * m_resolution); m_moveStep = qRound(scaledUiMoveStep); m_configGroup.writeEntry("moveToolStep", m_moveStep); } void MoveToolOptionsWidget::on_spinMoveScale_valueChanged(double UIMoveScale) { m_moveScale = UIMoveScale; m_configGroup.writeEntry("moveToolScale", m_moveScale); } void MoveToolOptionsWidget::on_cmbUnit_currentIndexChanged(int newUnit) { m_moveStepUnit = newUnit; updateUIUnit(newUnit); m_configGroup.writeEntry("moveToolUnit", newUnit); } void MoveToolOptionsWidget::on_radioSelectedLayer_toggled(bool checked) { Q_UNUSED(checked); setMoveToolMode(KisToolMove::MoveSelectedLayer); } void MoveToolOptionsWidget::on_radioFirstLayer_toggled(bool checked) { Q_UNUSED(checked); setMoveToolMode(KisToolMove::MoveFirstLayer); } void MoveToolOptionsWidget::on_radioGroup_toggled(bool checked) { Q_UNUSED(checked); setMoveToolMode(KisToolMove::MoveGroup); } void MoveToolOptionsWidget::setMoveToolMode(KisToolMove::MoveToolMode newMode) { m_moveToolMode = newMode; m_configGroup.writeEntry("moveToolMode", static_cast(newMode)); } void MoveToolOptionsWidget::on_chkShowCoordinates_toggled(bool checked) { m_showCoordinates = checked; m_configGroup.writeEntry("moveToolShowCoordinates", m_showCoordinates); } KisToolMove::MoveToolMode MoveToolOptionsWidget::mode() { return m_moveToolMode; } int MoveToolOptionsWidget::moveStep() { return m_moveStep; } double MoveToolOptionsWidget::moveScale() { return m_moveScale; } bool MoveToolOptionsWidget::showCoordinates() const { return m_showCoordinates; } void MoveToolOptionsWidget::setShowCoordinates(bool value) { chkShowCoordinates->setChecked(value); } void MoveToolOptionsWidget::slotSetTranslate(QPoint newPos) { translateXBox->setValue(newPos.x()); translateYBox->setValue(newPos.y()); } void MoveToolOptionsWidget::on_translateXBox_valueChanged(int arg1) { m_TranslateX = arg1; m_configGroup.writeEntry("moveToolChangedValueX", m_TranslateX); emit sigSetTranslateX(arg1); } void MoveToolOptionsWidget::on_translateYBox_valueChanged(int arg1) { m_TranslateY = arg1; m_configGroup.writeEntry("moveToolChangedValueY", m_TranslateY); emit sigSetTranslateY(arg1); } diff --git a/plugins/tools/basictools/kis_tool_movetooloptionswidget.h b/plugins/tools/basictools/kis_tool_movetooloptionswidget.h index f1434bfd20..7749f0e054 100644 --- a/plugins/tools/basictools/kis_tool_movetooloptionswidget.h +++ b/plugins/tools/basictools/kis_tool_movetooloptionswidget.h @@ -1,83 +1,85 @@ /* Copyright (C) 2012 Dan Leinir Turthra Jensen 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_MOVETOOLOPTIONSWIDGET_H #define KIS_TOOL_MOVETOOLOPTIONSWIDGET_H #include "ui_wdgmovetool.h" #include #include "kis_tool_move.h" class MoveToolOptionsWidget : public QWidget, public Ui::WdgMoveTool { Q_OBJECT public: MoveToolOptionsWidget(QWidget *parent, int resolution, QString toolId); int moveStep(); double moveScale(); KisToolMove::MoveToolMode mode(); bool showCoordinates() const; public Q_SLOTS: void setShowCoordinates(bool value); void slotSetTranslate(QPoint newPos); private Q_SLOTS: void on_spinMoveStep_valueChanged(double UIMoveStep); void on_spinMoveScale_valueChanged(double UIMoveScale); void on_cmbUnit_currentIndexChanged(int newUnit); void on_radioSelectedLayer_toggled(bool checked); void on_radioFirstLayer_toggled(bool checked); void on_radioGroup_toggled(bool checked); void on_chkShowCoordinates_toggled(bool checked); void on_translateXBox_valueChanged(int arg1); void on_translateYBox_valueChanged(int arg1); Q_SIGNALS: void showCoordinatesChanged(bool value); void sigSetTranslateX(int value); void sigSetTranslateY(int value); + void sigRequestCommitOffsetChanges(); + private: void updateUIUnit(int newUnit); void setMoveToolMode(KisToolMove::MoveToolMode newMode); int m_resolution; int m_moveStep; int m_moveStepUnit; qreal m_moveScale; KisToolMove::MoveToolMode m_moveToolMode; bool m_showCoordinates; int m_TranslateX; int m_TranslateY; KConfigGroup m_configGroup; }; #endif // KIS_TOOL_MOVETOOLOPTIONSWIDGET_H diff --git a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc index 58445b668e..f42e0928cb 100644 --- a/plugins/tools/selectiontools/kis_tool_select_contiguous.cc +++ b/plugins/tools/selectiontools/kis_tool_select_contiguous.cc @@ -1,254 +1,247 @@ /* * kis_tool_select_contiguous - part of Krayon^WKrita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2012 José Luis Vergara * 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 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_select_contiguous.h" #include #include #include #include #include #include #include #include #include #include "KoPointerEvent.h" #include "KoViewConverter.h" #include "kis_cursor.h" #include "kis_selection_manager.h" #include "kis_image.h" #include "canvas/kis_canvas2.h" #include "kis_layer.h" #include "kis_selection_options.h" #include "kis_paint_device.h" #include "kis_fill_painter.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include "kis_slider_spin_box.h" #include "tiles3/kis_hline_iterator.h" KisToolSelectContiguous::KisToolSelectContiguous(KoCanvasBase *canvas) : KisToolSelect(canvas, KisCursor::load("tool_contiguous_selection_cursor.png", 6, 6), i18n("Contiguous Area Selection")), m_fuzziness(20), m_sizemod(0), m_feather(0), m_limitToCurrentLayer(false) { setObjectName("tool_select_contiguous"); - connect(&m_widgetHelper, &KisSelectionToolConfigWidgetHelper::selectionActionChanged, - this, &KisToolSelectContiguous::setSelectionAction); } KisToolSelectContiguous::~KisToolSelectContiguous() { } void KisToolSelectContiguous::activate(ToolActivation toolActivation, const QSet &shapes) { - KisTool::activate(toolActivation, shapes); + KisToolSelect::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolSelectContiguous::beginPrimaryAction(KoPointerEvent *event) { KisToolSelectBase::beginPrimaryAction(event); KisPaintDeviceSP dev; if (!currentNode() || !(dev = currentNode()->projection()) || !currentNode()->visible() || !selectionEditable()) { event->ignore(); return; } QApplication::setOverrideCursor(KisCursor::waitCursor()); QPoint pos = convertToImagePixelCoordFloored(event); QRect rc = currentImage()->bounds(); KisFillPainter fillpainter(dev); fillpainter.setHeight(rc.height()); fillpainter.setWidth(rc.width()); fillpainter.setFillThreshold(m_fuzziness); fillpainter.setFeather(m_feather); fillpainter.setSizemod(m_sizemod); KisImageWSP image = currentImage(); KisPaintDeviceSP sourceDevice = m_limitToCurrentLayer ? dev : image->projection(); image->lock(); KisSelectionSP selection = fillpainter.createFloodSelection(pos.x(), pos.y(), sourceDevice); image->unlock(); // If we're not antialiasing, threshold the entire selection if (!antiAliasSelection()) { QRect r = selection->selectedExactRect(); if (r.isValid()) { KisHLineIteratorSP selectionIt = selection->pixelSelection()->createHLineIteratorNG(r.x(), r.y(), r.width()); for (qint32 y = 0; y < r.height(); y++) { do { if (selectionIt->rawData()[0] > 0) { selection->pixelSelection()->colorSpace()->setOpacity(selectionIt->rawData(), OPACITY_OPAQUE_U8, 1); } } while (selectionIt->nextPixel()); selectionIt->nextRow(); } } } KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (!kisCanvas || !selection->pixelSelection()) { QApplication::restoreOverrideCursor(); return; } selection->pixelSelection()->invalidateOutlineCache(); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Contiguous Area")); helper.selectPixelSelection(selection->pixelSelection(), selectionAction()); QApplication::restoreOverrideCursor(); } void KisToolSelectContiguous::paint(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(painter); Q_UNUSED(converter); } void KisToolSelectContiguous::slotSetFuzziness(int fuzziness) { m_fuzziness = fuzziness; m_configGroup.writeEntry("fuzziness", fuzziness); } void KisToolSelectContiguous::slotSetSizemod(int sizemod) { m_sizemod = sizemod; m_configGroup.writeEntry("sizemod", sizemod); } void KisToolSelectContiguous::slotSetFeather(int feather) { m_feather = feather; m_configGroup.writeEntry("feather", feather); } QWidget* KisToolSelectContiguous::createOptionWidget() { KisToolSelectBase::createOptionWidget(); KisSelectionOptions *selectionWidget = selectionOptionWidget(); selectionWidget->disableSelectionModeOption(); QVBoxLayout * l = dynamic_cast(selectionWidget->layout()); Q_ASSERT(l); if (l) { QGridLayout * gridLayout = new QGridLayout(); l->insertLayout(1, gridLayout); QLabel * lbl = new QLabel(i18n("Fuzziness: "), selectionWidget); gridLayout->addWidget(lbl, 0, 0, 1, 1); KisSliderSpinBox *input = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(input); input->setObjectName("fuzziness"); input->setRange(1, 100); input->setSingleStep(1); input->setExponentRatio(2); gridLayout->addWidget(input, 0, 1, 1, 1); lbl = new QLabel(i18n("Grow/shrink selection: "), selectionWidget); gridLayout->addWidget(lbl, 1, 0, 1, 1); KisSliderSpinBox *sizemod = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(sizemod); sizemod->setObjectName("sizemod"); //grow/shrink selection sizemod->setRange(-40, 40); sizemod->setSingleStep(1); gridLayout->addWidget(sizemod, 1, 1, 1, 1); lbl = new QLabel(i18n("Feathering radius: "), selectionWidget); gridLayout->addWidget(lbl, 2, 0, 1, 1); KisSliderSpinBox *feather = new KisSliderSpinBox(selectionWidget); Q_CHECK_PTR(feather); feather->setObjectName("feathering"); feather->setRange(0, 40); feather->setSingleStep(1); gridLayout->addWidget(feather, 2, 1, 1, 1); connect (input , SIGNAL(valueChanged(int)), this, SLOT(slotSetFuzziness(int) )); connect (sizemod, SIGNAL(valueChanged(int)), this, SLOT(slotSetSizemod(int) )); connect (feather, SIGNAL(valueChanged(int)), this, SLOT(slotSetFeather(int) )); QCheckBox* limitToCurrentLayer = new QCheckBox(i18n("Limit to current layer"), selectionWidget); l->insertWidget(4, limitToCurrentLayer); connect (limitToCurrentLayer, SIGNAL(stateChanged(int)), this, SLOT(slotLimitToCurrentLayer(int))); // load configuration settings into tool options input->setValue(m_configGroup.readEntry("fuzziness", 20)); // fuzziness sizemod->setValue( m_configGroup.readEntry("sizemod", 0)); //grow/shrink sizemod->setSuffix(i18n(" px")); feather->setValue(m_configGroup.readEntry("feather", 0)); feather->setSuffix(i18n(" px")); limitToCurrentLayer->setChecked(m_configGroup.readEntry("limitToCurrentLayer", false)); } return selectionWidget; } void KisToolSelectContiguous::slotLimitToCurrentLayer(int state) { if (state == Qt::PartiallyChecked) return; m_limitToCurrentLayer = (state == Qt::Checked); m_configGroup.writeEntry("limitToCurrentLayer", state); } - -void KisToolSelectContiguous::setSelectionAction(int action) -{ - changeSelectionAction(action); -} - - -QMenu* KisToolSelectContiguous::popupActionsMenu() +void KisToolSelectContiguous::resetCursorStyle() { - KisCanvas2 * kisCanvas = dynamic_cast(canvas()); - Q_ASSERT(kisCanvas); - - - return KisSelectionToolHelper::getSelectionContextMenu(kisCanvas); + if (selectionAction() == SELECTION_ADD) { + useCursor(KisCursor::load("tool_contiguous_selection_cursor_add.png", 6, 6)); + } else if (selectionAction() == SELECTION_SUBTRACT) { + useCursor(KisCursor::load("tool_contiguous_selection_cursor_sub.png", 6, 6)); + } else { + KisToolSelect::resetCursorStyle(); + } } diff --git a/plugins/tools/selectiontools/kis_tool_select_contiguous.h b/plugins/tools/selectiontools/kis_tool_select_contiguous.h index 717cbbd284..c3f5b875a3 100644 --- a/plugins/tools/selectiontools/kis_tool_select_contiguous.h +++ b/plugins/tools/selectiontools/kis_tool_select_contiguous.h @@ -1,96 +1,95 @@ /* * kis_tool_select_contiguous.h - part of KImageShop^WKrayon^Krita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2002 Patrick Julien * 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 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_SELECT_CONTIGUOUS_H__ #define __KIS_TOOL_SELECT_CONTIGUOUS_H__ #include "KoToolFactoryBase.h" #include "kis_tool_select_base.h" #include #include #include /** * The 'magic wand' selection tool -- in fact just * a floodfill that only creates a selection. */ class KisToolSelectContiguous : public KisToolSelect { Q_OBJECT public: KisToolSelectContiguous(KoCanvasBase *canvas); ~KisToolSelectContiguous() override; QWidget* createOptionWidget() override; void paint(QPainter &painter, const KoViewConverter &converter) override; void beginPrimaryAction(KoPointerEvent *event) override; - QMenu* popupActionsMenu() override; + void resetCursorStyle(); protected: bool wantsAutoScroll() const override { return false; } public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; virtual void slotSetFuzziness(int); virtual void slotSetSizemod(int); virtual void slotSetFeather(int); virtual void slotLimitToCurrentLayer(int); - void setSelectionAction(int); //virtual bool antiAliasSelection(); protected: using KisToolSelectBase::m_widgetHelper; private: int m_fuzziness; int m_sizemod; int m_feather; bool m_limitToCurrentLayer; KConfigGroup m_configGroup; }; class KisToolSelectContiguousFactory : public KoToolFactoryBase { public: KisToolSelectContiguousFactory() : KoToolFactoryBase("KisToolSelectContiguous") { setToolTip(i18n("Contiguous Selection Tool")); setSection(TOOL_TYPE_SELECTION); setIconName(koIconNameCStr("tool_contiguous_selection")); setPriority(4); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolSelectContiguousFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectContiguous(canvas); } }; #endif //__KIS_TOOL_SELECT_CONTIGUOUS_H__ diff --git a/plugins/tools/selectiontools/kis_tool_select_elliptical.cc b/plugins/tools/selectiontools/kis_tool_select_elliptical.cc index 30e1ea1e43..831551060b 100644 --- a/plugins/tools/selectiontools/kis_tool_select_elliptical.cc +++ b/plugins/tools/selectiontools/kis_tool_select_elliptical.cc @@ -1,108 +1,103 @@ /* * kis_tool_select_elliptical.cc -- part of Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2007 Sven Langkamp * 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 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_select_elliptical.h" #include #include "kis_painter.h" #include #include "kis_selection_options.h" #include "kis_canvas2.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include "kis_shape_tool_helper.h" #include "KisViewManager.h" #include "kis_selection_manager.h" __KisToolSelectEllipticalLocal::__KisToolSelectEllipticalLocal(KoCanvasBase *canvas) : KisToolEllipseBase(canvas, KisToolEllipseBase::SELECT, KisCursor::load("tool_elliptical_selection_cursor.png", 6, 6)) { setObjectName("tool_select_elliptical"); } bool __KisToolSelectEllipticalLocal::hasUserInteractionRunning() const { return false; } void __KisToolSelectEllipticalLocal::finishRect(const QRectF &rect, qreal roundCornersX, qreal roundCornersY) { Q_UNUSED(roundCornersX); Q_UNUSED(roundCornersY); KisCanvas2 * kisCanvas = dynamic_cast(canvas()); Q_ASSERT(kisCanvas); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Ellipse")); if (helper.tryDeselectCurrentSelection(pixelToView(rect), selectionAction())) { return; } if (selectionMode() == PIXEL_SELECTION) { KisPixelSelectionSP tmpSel = new KisPixelSelection(); KisPainter painter(tmpSel); painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace())); painter.setAntiAliasPolygonFill(antiAliasSelection()); painter.setFillStyle(KisPainter::FillStyleForegroundColor); painter.setStrokeStyle(KisPainter::StrokeStyleNone); painter.paintEllipse(rect); QPainterPath cache; cache.addEllipse(rect); tmpSel->setOutlineCache(cache); helper.selectPixelSelection(tmpSel, selectionAction()); } else { QRectF ptRect = convertToPt(rect); KoShape* shape = KisShapeToolHelper::createEllipseShape(ptRect); helper.addSelectionShape(shape, selectionAction()); } } KisToolSelectElliptical::KisToolSelectElliptical(KoCanvasBase *canvas): KisToolSelectEllipticalTemplate(canvas, i18n("Elliptical Selection")) { - connect(&m_widgetHelper, &KisSelectionToolConfigWidgetHelper::selectionActionChanged, - this, &KisToolSelectElliptical::setSelectionAction); } - -void KisToolSelectElliptical::setSelectionAction(int action) +void KisToolSelectElliptical::resetCursorStyle() { - changeSelectionAction(action); + if (selectionAction() == SELECTION_ADD) { + useCursor(KisCursor::load("tool_elliptical_selection_cursor_add.png", 6, 6)); + } else if (selectionAction() == SELECTION_SUBTRACT) { + useCursor(KisCursor::load("tool_elliptical_selection_cursor_sub.png", 6, 6)); + } else { + KisToolSelectBase<__KisToolSelectEllipticalLocal>::resetCursorStyle(); + } } -QMenu* KisToolSelectElliptical::popupActionsMenu() -{ - KisCanvas2 * kisCanvas = dynamic_cast(canvas()); - Q_ASSERT(kisCanvas); - - - return KisSelectionToolHelper::getSelectionContextMenu(kisCanvas); -} diff --git a/plugins/tools/selectiontools/kis_tool_select_elliptical.h b/plugins/tools/selectiontools/kis_tool_select_elliptical.h index ed425e90df..4b29742910 100644 --- a/plugins/tools/selectiontools/kis_tool_select_elliptical.h +++ b/plugins/tools/selectiontools/kis_tool_select_elliptical.h @@ -1,94 +1,91 @@ /* * kis_tool_select_elliptical.h - part of Krayon^WKrita * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * * 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 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_SELECT_ELLIPTICAL_H__ #define __KIS_TOOL_SELECT_ELLIPTICAL_H__ #include "KoToolFactoryBase.h" #include "kis_tool_ellipse_base.h" #include #include "kis_selection_tool_config_widget_helper.h" #include #include #include #include class __KisToolSelectEllipticalLocal : public KisToolEllipseBase { Q_OBJECT public: __KisToolSelectEllipticalLocal(KoCanvasBase *canvas); bool hasUserInteractionRunning() const; protected: virtual SelectionMode selectionMode() const = 0; virtual SelectionAction selectionAction() const = 0; virtual bool antiAliasSelection() const = 0; private: void finishRect(const QRectF &rect, qreal roundCornersX, qreal roundCornersY) override; }; typedef KisToolSelectBase<__KisToolSelectEllipticalLocal> KisToolSelectEllipticalTemplate; class KisToolSelectElliptical : public KisToolSelectEllipticalTemplate { Q_OBJECT public: KisToolSelectElliptical(KoCanvasBase* canvas); - QMenu* popupActionsMenu() override; - -public Q_SLOTS: - void setSelectionAction(int); + void resetCursorStyle(); }; class KisToolSelectEllipticalFactory : public KoToolFactoryBase { public: KisToolSelectEllipticalFactory() : KoToolFactoryBase("KisToolSelectElliptical") { setToolTip(i18n("Elliptical Selection Tool")); setSection(TOOL_TYPE_SELECTION); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setIconName(koIconNameCStr("tool_elliptical_selection")); setShortcut(QKeySequence(Qt::Key_J)); setPriority(1); } ~KisToolSelectEllipticalFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectElliptical(canvas); } }; #endif //__KIS_TOOL_SELECT_ELLIPTICAL_H__ diff --git a/plugins/tools/selectiontools/kis_tool_select_outline.cc b/plugins/tools/selectiontools/kis_tool_select_outline.cc index 3b3f62d942..0fb9f92803 100644 --- a/plugins/tools/selectiontools/kis_tool_select_outline.cc +++ b/plugins/tools/selectiontools/kis_tool_select_outline.cc @@ -1,280 +1,276 @@ /* * kis_tool_select_freehand.h - part of Krayon^WKrita * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * 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 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_select_outline.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_painter.h" #include #include "canvas/kis_canvas2.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include "kis_algebra_2d.h" #define FEEDBACK_LINE_WIDTH 2 KisToolSelectOutline::KisToolSelectOutline(KoCanvasBase * canvas) : KisToolSelect(canvas, KisCursor::load("tool_outline_selection_cursor.png", 5, 5), i18n("Outline Selection")), m_continuedMode(false) { - connect(&m_widgetHelper, &KisSelectionToolConfigWidgetHelper::selectionActionChanged, - this, &KisToolSelectOutline::setSelectionAction); } KisToolSelectOutline::~KisToolSelectOutline() { } void KisToolSelectOutline::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Control) { m_continuedMode = true; } KisToolSelect::keyPressEvent(event); } void KisToolSelectOutline::keyReleaseEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Control || !(event->modifiers() & Qt::ControlModifier)) { m_continuedMode = false; if (mode() != PAINT_MODE && !m_points.isEmpty()) { finishSelectionAction(); } } KisToolSelect::keyReleaseEvent(event); } void KisToolSelectOutline::mouseMoveEvent(KoPointerEvent *event) { KisToolSelect::mouseMoveEvent(event); if (selectionDragInProgress()) return; m_lastCursorPos = convertToPixelCoord(event); if (m_continuedMode && mode() != PAINT_MODE) { updateContinuedMode(); } } void KisToolSelectOutline::beginPrimaryAction(KoPointerEvent *event) { KisToolSelectBase::beginPrimaryAction(event); if (selectionDragInProgress()) return; if (!selectionEditable()) { event->ignore(); return; } setMode(KisTool::PAINT_MODE); if (m_continuedMode && !m_points.isEmpty()) { m_paintPath.lineTo(pixelToView(convertToPixelCoord(event))); } else { m_paintPath.moveTo(pixelToView(convertToPixelCoord(event))); } m_points.append(convertToPixelCoord(event)); } void KisToolSelectOutline::continuePrimaryAction(KoPointerEvent *event) { KisToolSelectBase::continuePrimaryAction(event); if (selectionDragInProgress()) return; CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); QPointF point = convertToPixelCoord(event); m_paintPath.lineTo(pixelToView(point)); m_points.append(point); updateFeedback(); } void KisToolSelectOutline::endPrimaryAction(KoPointerEvent *event) { const bool hadMoveInProgress = selectionDragInProgress(); KisToolSelectBase::endPrimaryAction(event); if (hadMoveInProgress) return; CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); if (!m_continuedMode) { finishSelectionAction(); } } void KisToolSelectOutline::finishSelectionAction() { KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); kisCanvas->updateCanvas(); QRectF boundingViewRect = pixelToView(KisAlgebra2D::accumulateBounds(m_points)); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select by Outline")); if (m_points.count() > 2 && !helper.tryDeselectCurrentSelection(boundingViewRect, selectionAction())) { QApplication::setOverrideCursor(KisCursor::waitCursor()); if (selectionMode() == PIXEL_SELECTION) { KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); KisPainter painter(tmpSel); painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace())); painter.setAntiAliasPolygonFill(antiAliasSelection()); painter.setFillStyle(KisPainter::FillStyleForegroundColor); painter.setStrokeStyle(KisPainter::StrokeStyleNone); painter.paintPolygon(m_points); QPainterPath cache; cache.addPolygon(m_points); cache.closeSubpath(); tmpSel->setOutlineCache(cache); helper.selectPixelSelection(tmpSel, selectionAction()); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(m_points[0])); for (int i = 1; i < m_points.count(); i++) path->lineTo(resolutionMatrix.map(m_points[i])); path->close(); path->normalize(); helper.addSelectionShape(path, selectionAction()); } QApplication::restoreOverrideCursor(); } m_points.clear(); m_paintPath = QPainterPath(); } void KisToolSelectOutline::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if ((mode() == KisTool::PAINT_MODE || m_continuedMode) && !m_points.isEmpty()) { QPainterPath outline = m_paintPath; if (m_continuedMode && mode() != KisTool::PAINT_MODE) { outline.lineTo(pixelToView(m_lastCursorPos)); } paintToolOutline(&gc, outline); } } void KisToolSelectOutline::updateFeedback() { if (m_points.count() > 1) { qint32 lastPointIndex = m_points.count() - 1; QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_points[lastPointIndex]).normalized(); updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH); updateCanvasPixelRect(updateRect); } } void KisToolSelectOutline::updateContinuedMode() { if (!m_points.isEmpty()) { qint32 lastPointIndex = m_points.count() - 1; QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_lastCursorPos).normalized(); updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH); updateCanvasPixelRect(updateRect); } } void KisToolSelectOutline::deactivate() { KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); kisCanvas->updateCanvas(); m_continuedMode = false; KisTool::deactivate(); } -void KisToolSelectOutline::setSelectionAction(int action) +void KisToolSelectOutline::resetCursorStyle() { - changeSelectionAction(action); + if (selectionAction() == SELECTION_ADD) { + useCursor(KisCursor::load("tool_outline_selection_cursor_add.png", 6, 6)); + } else if (selectionAction() == SELECTION_SUBTRACT) { + useCursor(KisCursor::load("tool_outline_selection_cursor_sub.png", 6, 6)); + } else { + KisToolSelect::resetCursorStyle(); + } } -QMenu* KisToolSelectOutline::popupActionsMenu() -{ - KisCanvas2 * kisCanvas = dynamic_cast(canvas()); - Q_ASSERT(kisCanvas); - - - return KisSelectionToolHelper::getSelectionContextMenu(kisCanvas); -} diff --git a/plugins/tools/selectiontools/kis_tool_select_outline.h b/plugins/tools/selectiontools/kis_tool_select_outline.h index 799bf691a2..27c894a0d5 100644 --- a/plugins/tools/selectiontools/kis_tool_select_outline.h +++ b/plugins/tools/selectiontools/kis_tool_select_outline.h @@ -1,94 +1,93 @@ /* * kis_tool_select_freehand.h - part of Krayon^WKrita * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * 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 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_SELECT_OUTLINE_H_ #define KIS_TOOL_SELECT_OUTLINE_H_ #include #include #include #include class QPainterPath; class KisToolSelectOutline : public KisToolSelect { Q_OBJECT public: KisToolSelectOutline(KoCanvasBase *canvas); ~KisToolSelectOutline() override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void paint(QPainter& gc, const KoViewConverter &converter) override; void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; - QMenu* popupActionsMenu() override; + void resetCursorStyle(); public Q_SLOTS: void deactivate() override; - void setSelectionAction(int); protected: using KisToolSelectBase::m_widgetHelper; private: void finishSelectionAction(); void updateFeedback(); void updateContinuedMode(); void updateCanvas(); QPainterPath m_paintPath; vQPointF m_points; bool m_continuedMode; QPointF m_lastCursorPos; }; class KisToolSelectOutlineFactory : public KoToolFactoryBase { public: KisToolSelectOutlineFactory() : KoToolFactoryBase("KisToolSelectOutline") { setToolTip(i18n("Outline Selection Tool")); setSection(TOOL_TYPE_SELECTION); setIconName(koIconNameCStr("tool_outline_selection")); setPriority(3); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolSelectOutlineFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectOutline(canvas); } }; #endif //__selecttoolfreehand_h__ diff --git a/plugins/tools/selectiontools/kis_tool_select_path.cc b/plugins/tools/selectiontools/kis_tool_select_path.cc index e647891a65..0ace61079d 100644 --- a/plugins/tools/selectiontools/kis_tool_select_path.cc +++ b/plugins/tools/selectiontools/kis_tool_select_path.cc @@ -1,176 +1,187 @@ /* * Copyright (c) 2007 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_tool_select_path.h" #include #include "kis_cursor.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_selection_options.h" #include "kis_canvas_resource_provider.h" #include "kis_canvas2.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include KisToolSelectPath::KisToolSelectPath(KoCanvasBase * canvas) : KisToolSelectBase(canvas, KisCursor::load("tool_polygonal_selection_cursor.png", 6, 6), i18n("Select path"), (KisTool*) (new __KisToolSelectPathLocalTool(canvas, this))) { } void KisToolSelectPath::requestStrokeEnd() { localTool()->endPathWithoutLastPoint(); } void KisToolSelectPath::requestStrokeCancellation() { localTool()->cancelPath(); } void KisToolSelectPath::mousePressEvent(KoPointerEvent* event) { if (!selectionEditable()) return; DelegatedSelectPathTool::mousePressEvent(event); } // Install an event filter to catch right-click events. // This code is duplicated in kis_tool_path.cc bool KisToolSelectPath::eventFilter(QObject *obj, QEvent *event) { Q_UNUSED(obj); if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) { QMouseEvent *mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::RightButton) { localTool()->removeLastPoint(); return true; } } else if (event->type() == QEvent::TabletPress) { QTabletEvent *tabletEvent = static_cast(event); if (tabletEvent->button() == Qt::RightButton) { localTool()->removeLastPoint(); return true; } } return false; } QList > KisToolSelectPath::createOptionWidgets() { QList > widgetsList = DelegatedSelectPathTool::createOptionWidgets(); QList > filteredWidgets; Q_FOREACH (QWidget* widget, widgetsList) { if (widget->objectName() != "Stroke widget") { filteredWidgets.push_back(widget); } } return filteredWidgets; } void KisDelegatedSelectPathWrapper::beginPrimaryAction(KoPointerEvent *event) { mousePressEvent(event); } void KisDelegatedSelectPathWrapper::continuePrimaryAction(KoPointerEvent *event){ mouseMoveEvent(event); } void KisDelegatedSelectPathWrapper::endPrimaryAction(KoPointerEvent *event) { mouseReleaseEvent(event); } bool KisDelegatedSelectPathWrapper::hasUserInteractionRunning() const { /** * KoCreatePathTool doesn't support moving interventions from KisToolselectBase, * because it doesn't use begin/continue/endPrimaryAction and uses direct event * handling instead. * * TODO: refactor KoCreatePathTool and port it to action infrastructure */ return true; } __KisToolSelectPathLocalTool::__KisToolSelectPathLocalTool(KoCanvasBase * canvas, KisToolSelectPath* parentTool) : KoCreatePathTool(canvas), m_selectionTool(parentTool) { setEnableClosePathShortcut(false); } void __KisToolSelectPathLocalTool::paintPath(KoPathShape &pathShape, QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(converter); KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (!kisCanvas) return; QTransform matrix; matrix.scale(kisCanvas->image()->xRes(), kisCanvas->image()->yRes()); matrix.translate(pathShape.position().x(), pathShape.position().y()); m_selectionTool->paintToolOutline(&painter, m_selectionTool->pixelToView(matrix.map(pathShape.outline()))); } void __KisToolSelectPathLocalTool::addPathShape(KoPathShape* pathShape) { pathShape->normalize(); pathShape->close(); KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (!kisCanvas) return; KisImageWSP image = kisCanvas->image(); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select by Bezier Curve")); if (m_selectionTool->selectionMode() == PIXEL_SELECTION) { KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); KisPainter painter(tmpSel); painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace())); painter.setFillStyle(KisPainter::FillStyleForegroundColor); painter.setAntiAliasPolygonFill(m_selectionTool->antiAliasSelection()); painter.setStrokeStyle(KisPainter::StrokeStyleNone); QTransform matrix; matrix.scale(image->xRes(), image->yRes()); matrix.translate(pathShape->position().x(), pathShape->position().y()); QPainterPath path = matrix.map(pathShape->outline()); painter.fillPainterPath(path); tmpSel->setOutlineCache(path); helper.selectPixelSelection(tmpSel, m_selectionTool->selectionAction()); delete pathShape; } else { helper.addSelectionShape(pathShape, m_selectionTool->selectionAction()); } } +void KisToolSelectPath::resetCursorStyle() +{ + if (selectionAction() == SELECTION_ADD) { + useCursor(KisCursor::load("tool_polygonal_selection_cursor_add.png", 6, 6)); + } else if (selectionAction() == SELECTION_SUBTRACT) { + useCursor(KisCursor::load("tool_polygonal_selection_cursor_sub.png", 6, 6)); + } else { + KisToolSelectBase::resetCursorStyle(); + } +} + diff --git a/plugins/tools/selectiontools/kis_tool_select_path.h b/plugins/tools/selectiontools/kis_tool_select_path.h index 6b811c3cdc..4382299545 100644 --- a/plugins/tools/selectiontools/kis_tool_select_path.h +++ b/plugins/tools/selectiontools/kis_tool_select_path.h @@ -1,109 +1,110 @@ /* * Copyright (c) 2007 Sven Langkamp * 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 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_SELECT_PATH_H_ #define KIS_TOOL_SELECT_PATH_H_ #include #include #include "kis_tool_select_base.h" #include "kis_delegated_tool.h" #include class KoCanvasBase; class KisToolSelectPath; class __KisToolSelectPathLocalTool : public KoCreatePathTool { public: __KisToolSelectPathLocalTool(KoCanvasBase * canvas, KisToolSelectPath* parentTool); void paintPath(KoPathShape &path, QPainter &painter, const KoViewConverter &converter) override; void addPathShape(KoPathShape* pathShape) override; using KoCreatePathTool::createOptionWidgets; using KoCreatePathTool::endPathWithoutLastPoint; using KoCreatePathTool::endPath; using KoCreatePathTool::cancelPath; using KoCreatePathTool::removeLastPoint; private: KisToolSelectPath* const m_selectionTool; }; typedef KisDelegatedTool DelegatedSelectPathTool; struct KisDelegatedSelectPathWrapper : public DelegatedSelectPathTool { KisDelegatedSelectPathWrapper(KoCanvasBase *canvas, const QCursor &cursor, KisTool* delegateTool) : DelegatedSelectPathTool(canvas, cursor, (__KisToolSelectPathLocalTool*) delegateTool) { } // If an event is explicitly forwarded only as an action (e.g. shift-click is captured by "change size") // we will receive a primary action but no mousePressEvent. Thus these events must be explicitly forwarded. void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; bool hasUserInteractionRunning() const; }; class KisToolSelectPath : public KisToolSelectBase { Q_OBJECT public: KisToolSelectPath(KoCanvasBase * canvas); void mousePressEvent(KoPointerEvent* event) override; bool eventFilter(QObject *obj, QEvent *event) override; + void resetCursorStyle(); protected: void requestStrokeCancellation() override; void requestStrokeEnd() override; friend class __KisToolSelectPathLocalTool; QList > createOptionWidgets() override; }; class KisToolSelectPathFactory : public KoToolFactoryBase { public: KisToolSelectPathFactory() : KoToolFactoryBase("KisToolSelectPath") { setToolTip(i18n("Bezier Curve Selection Tool")); setSection(TOOL_TYPE_SELECTION); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setIconName(koIconNameCStr("tool_path_selection")); setPriority(6); } ~KisToolSelectPathFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectPath(canvas); } }; #endif // KIS_TOOL_SELECT_PATH_H_ diff --git a/plugins/tools/selectiontools/kis_tool_select_polygonal.cc b/plugins/tools/selectiontools/kis_tool_select_polygonal.cc index ea79b6e03e..fe8b6b8e5c 100644 --- a/plugins/tools/selectiontools/kis_tool_select_polygonal.cc +++ b/plugins/tools/selectiontools/kis_tool_select_polygonal.cc @@ -1,112 +1,106 @@ /* * kis_tool_select_polygonal.h - part of Krayon^WKrita * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * 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 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_select_polygonal.h" #include #include "kis_painter.h" #include #include "kis_selection_options.h" #include "kis_canvas2.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include "kis_shape_tool_helper.h" #include "KisViewManager.h" #include "kis_selection_manager.h" __KisToolSelectPolygonalLocal::__KisToolSelectPolygonalLocal(KoCanvasBase *canvas) : KisToolPolylineBase(canvas, KisToolPolylineBase::SELECT, KisCursor::load("tool_polygonal_selection_cursor.png", 6, 6)) { setObjectName("tool_select_polygonal"); } void __KisToolSelectPolygonalLocal::finishPolyline(const QVector &points) { KisCanvas2 * kisCanvas = dynamic_cast(canvas()); Q_ASSERT(kisCanvas); if (!kisCanvas) return; KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Polygon")); if (selectionMode() == PIXEL_SELECTION) { KisPixelSelectionSP tmpSel = new KisPixelSelection(); KisPainter painter(tmpSel); painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace())); painter.setAntiAliasPolygonFill(antiAliasSelection()); painter.setFillStyle(KisPainter::FillStyleForegroundColor); painter.setStrokeStyle(KisPainter::StrokeStyleNone); painter.paintPolygon(points); QPainterPath cache; cache.addPolygon(points); cache.closeSubpath(); tmpSel->setOutlineCache(cache); helper.selectPixelSelection(tmpSel, selectionAction()); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(points[0])); for (int i = 1; i < points.count(); i++) path->lineTo(resolutionMatrix.map(points[i])); path->close(); path->normalize(); helper.addSelectionShape(path, selectionAction()); } } KisToolSelectPolygonal::KisToolSelectPolygonal(KoCanvasBase *canvas): KisToolSelectBase<__KisToolSelectPolygonalLocal>(canvas, i18n("Polygonal Selection")) { - connect(&m_widgetHelper, &KisSelectionToolConfigWidgetHelper::selectionActionChanged, - this, &KisToolSelectPolygonal::setSelectionAction); } -void KisToolSelectPolygonal::setSelectionAction(int action) +void KisToolSelectPolygonal::resetCursorStyle() { - changeSelectionAction(action); -} - - -QMenu* KisToolSelectPolygonal::popupActionsMenu() -{ - KisCanvas2 * kisCanvas = dynamic_cast(canvas()); - Q_ASSERT(kisCanvas); - - - return KisSelectionToolHelper::getSelectionContextMenu(kisCanvas); + if (selectionAction() == SELECTION_ADD) { + useCursor(KisCursor::load("tool_polygonal_selection_cursor_add.png", 6, 6)); + } else if (selectionAction() == SELECTION_SUBTRACT) { + useCursor(KisCursor::load("tool_polygonal_selection_cursor_sub.png", 6, 6)); + } else { + KisToolSelectBase<__KisToolSelectPolygonalLocal>::resetCursorStyle(); + } } diff --git a/plugins/tools/selectiontools/kis_tool_select_polygonal.h b/plugins/tools/selectiontools/kis_tool_select_polygonal.h index 7cb6359ad4..1777407ac0 100644 --- a/plugins/tools/selectiontools/kis_tool_select_polygonal.h +++ b/plugins/tools/selectiontools/kis_tool_select_polygonal.h @@ -1,81 +1,78 @@ /* * kis_tool_select_polygonal.h - part of Krayon^WKrita * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * 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 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_SELECT_POLYGONAL_H_ #define KIS_TOOL_SELECT_POLYGONAL_H_ #include "KoToolFactoryBase.h" #include "kis_tool_polyline_base.h" #include #include "kis_selection_tool_config_widget_helper.h" #include class __KisToolSelectPolygonalLocal : public KisToolPolylineBase { Q_OBJECT public: __KisToolSelectPolygonalLocal(KoCanvasBase *canvas); protected: virtual SelectionMode selectionMode() const = 0; virtual SelectionAction selectionAction() const = 0; virtual bool antiAliasSelection() const = 0; private: void finishPolyline(const QVector &points) override; private: }; class KisToolSelectPolygonal : public KisToolSelectBase<__KisToolSelectPolygonalLocal> { Q_OBJECT public: KisToolSelectPolygonal(KoCanvasBase* canvas); - QMenu* popupActionsMenu() override; - -public Q_SLOTS: - void setSelectionAction(int); + void resetCursorStyle(); }; class KisToolSelectPolygonalFactory : public KoToolFactoryBase { public: KisToolSelectPolygonalFactory() : KoToolFactoryBase("KisToolSelectPolygonal") { setToolTip(i18n("Polygonal Selection Tool")); setSection(TOOL_TYPE_SELECTION); setIconName(koIconNameCStr("tool_polygonal_selection")); setPriority(2); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolSelectPolygonalFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectPolygonal(canvas); } }; #endif //__selecttoolpolygonal_h__ diff --git a/plugins/tools/selectiontools/kis_tool_select_rectangular.cc b/plugins/tools/selectiontools/kis_tool_select_rectangular.cc index c6b703cb51..cfb50e1ea5 100644 --- a/plugins/tools/selectiontools/kis_tool_select_rectangular.cc +++ b/plugins/tools/selectiontools/kis_tool_select_rectangular.cc @@ -1,125 +1,120 @@ /* * kis_tool_select_rectangular.cc -- part of Krita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2001 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Sven Langkamp * 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 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_select_rectangular.h" #include "kis_painter.h" #include #include "kis_selection_options.h" #include "kis_canvas2.h" #include "kis_pixel_selection.h" #include "kis_selection_tool_helper.h" #include "kis_shape_tool_helper.h" #include "KisViewManager.h" #include "kis_selection_manager.h" __KisToolSelectRectangularLocal::__KisToolSelectRectangularLocal(KoCanvasBase * canvas) : KisToolRectangleBase(canvas, KisToolRectangleBase::SELECT, KisCursor::load("tool_rectangular_selection_cursor.png", 6, 6)) { setObjectName("tool_select_rectangular"); } bool __KisToolSelectRectangularLocal::hasUserInteractionRunning() const { return false; } void __KisToolSelectRectangularLocal::finishRect(const QRectF& rect, qreal roundCornersX, qreal roundCornersY) { KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (!kisCanvas) return; KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Rectangle")); QRect rc(rect.normalized().toRect()); if (helper.tryDeselectCurrentSelection(pixelToView(rc), selectionAction())) { return; } if (helper.canShortcutToNoop(rc, selectionAction())) { return; } if (selectionMode() == PIXEL_SELECTION) { if (rc.isValid()) { KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); QPainterPath cache; if (roundCornersX > 0 || roundCornersY > 0) { cache.addRoundedRect(rc, roundCornersX, roundCornersY); } else { cache.addRect(rc); } { KisPainter painter(tmpSel); painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace())); painter.setAntiAliasPolygonFill(true); painter.setFillStyle(KisPainter::FillStyleForegroundColor); painter.setStrokeStyle(KisPainter::StrokeStyleNone); painter.paintPainterPath(cache); } tmpSel->setOutlineCache(cache); helper.selectPixelSelection(tmpSel, selectionAction()); } } else { QRectF documentRect = convertToPt(rc); const qreal docRoundCornersX = convertToPt(roundCornersX); const qreal docRoundCornersY = convertToPt(roundCornersY); helper.addSelectionShape(KisShapeToolHelper::createRectangleShape(documentRect, docRoundCornersX, docRoundCornersY), selectionAction()); } } KisToolSelectRectangular::KisToolSelectRectangular(KoCanvasBase *canvas): KisToolSelectBase<__KisToolSelectRectangularLocal>(canvas, i18n("Rectangular Selection")) { - connect(&m_widgetHelper, &KisSelectionToolConfigWidgetHelper::selectionActionChanged, - this, &KisToolSelectRectangular::setSelectionAction); } -void KisToolSelectRectangular::setSelectionAction(int action) +void KisToolSelectRectangular::resetCursorStyle() { - changeSelectionAction(action); -} - -QMenu* KisToolSelectRectangular::popupActionsMenu() -{ - KisCanvas2 * kisCanvas = dynamic_cast(canvas()); - Q_ASSERT(kisCanvas); - - - return KisSelectionToolHelper::getSelectionContextMenu(kisCanvas); + if (selectionAction() == SELECTION_ADD) { + useCursor(KisCursor::load("tool_rectangular_selection_cursor_add.png", 6, 6)); + } else if (selectionAction() == SELECTION_SUBTRACT) { + useCursor(KisCursor::load("tool_rectangular_selection_cursor_sub.png", 6, 6)); + } else { + KisToolSelectBase<__KisToolSelectRectangularLocal>::resetCursorStyle(); + } } diff --git a/plugins/tools/selectiontools/kis_tool_select_rectangular.h b/plugins/tools/selectiontools/kis_tool_select_rectangular.h index 987a9fb7dc..7effa6e42e 100644 --- a/plugins/tools/selectiontools/kis_tool_select_rectangular.h +++ b/plugins/tools/selectiontools/kis_tool_select_rectangular.h @@ -1,90 +1,86 @@ /* * kis_tool_select_rectangular.h - part of Krita * * Copyright (c) 1999 Michael Koch * 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_TOOL_SELECT_RECTANGULAR_H_ #define KIS_TOOL_SELECT_RECTANGULAR_H_ #include "KoToolFactoryBase.h" #include "kis_tool_rectangle_base.h" #include #include "kis_selection_tool_config_widget_helper.h" #include #include class __KisToolSelectRectangularLocal : public KisToolRectangleBase { Q_OBJECT public: __KisToolSelectRectangularLocal(KoCanvasBase * canvas); - bool hasUserInteractionRunning() const -; - + bool hasUserInteractionRunning() const; protected: virtual SelectionMode selectionMode() const = 0; virtual SelectionAction selectionAction() const = 0; private: void finishRect(const QRectF& rect, qreal roundCornersX, qreal roundCornersY) override; }; class KisToolSelectRectangular : public KisToolSelectBase<__KisToolSelectRectangularLocal> { Q_OBJECT public: KisToolSelectRectangular(KoCanvasBase* canvas); - QMenu* popupActionsMenu() override; -public Q_SLOTS: - void setSelectionAction(int); + void resetCursorStyle(); }; class KisToolSelectRectangularFactory : public KoToolFactoryBase { public: KisToolSelectRectangularFactory() : KoToolFactoryBase("KisToolSelectRectangular") { setToolTip(i18n("Rectangular Selection Tool")); setSection(TOOL_TYPE_SELECTION); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setIconName(koIconNameCStr("tool_rect_selection")); setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R)); setPriority(0); } ~KisToolSelectRectangularFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectRectangular(canvas); } }; #endif // KIS_TOOL_SELECT_RECTANGULAR_H_ diff --git a/plugins/tools/selectiontools/kis_tool_select_similar.cc b/plugins/tools/selectiontools/kis_tool_select_similar.cc index 9bec9d60ec..72a01e55b5 100644 --- a/plugins/tools/selectiontools/kis_tool_select_similar.cc +++ b/plugins/tools/selectiontools/kis_tool_select_similar.cc @@ -1,189 +1,184 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 Boudewijn Rempt * 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 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_select_similar.h" #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include #include "kis_selection_tool_helper.h" #include "kis_slider_spin_box.h" #include "kis_iterator_ng.h" #include "kis_image.h" void selectByColor(KisPaintDeviceSP dev, KisPixelSelectionSP selection, const quint8 *c, int fuzziness, const QRect & rc) { if (rc.isEmpty()) { return; } // XXX: Multithread this! qint32 x, y, w, h; x = rc.x(); y = rc.y(); w = rc.width(); h = rc.height(); const KoColorSpace * cs = dev->colorSpace(); KisHLineConstIteratorSP hiter = dev->createHLineConstIteratorNG(x, y, w); KisHLineIteratorSP selIter = selection->createHLineIteratorNG(x, y, w); for (int row = y; row < y + h; ++row) { do { //if (dev->colorSpace()->hasAlpha()) // opacity = dev->colorSpace()->alpha(hiter->rawData()); if (fuzziness == 1) { if (memcmp(c, hiter->oldRawData(), cs->pixelSize()) == 0) { *(selIter->rawData()) = MAX_SELECTED; } } else { quint8 match = cs->difference(c, hiter->oldRawData()); if (match <= fuzziness) { *(selIter->rawData()) = MAX_SELECTED; } } } while (hiter->nextPixel() && selIter->nextPixel()); hiter->nextRow(); selIter->nextRow(); } } KisToolSelectSimilar::KisToolSelectSimilar(KoCanvasBase * canvas) : KisToolSelect(canvas, KisCursor::load("tool_similar_selection_cursor.png", 6, 6), i18n("Similar Color Selection")), m_fuzziness(20) { - connect(&m_widgetHelper, &KisSelectionToolConfigWidgetHelper::selectionActionChanged, - this, &KisToolSelectSimilar::setSelectionAction); } void KisToolSelectSimilar::activate(ToolActivation toolActivation, const QSet &shapes) { - KisTool::activate(toolActivation, shapes); + KisToolSelect::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolSelectSimilar::beginPrimaryAction(KoPointerEvent *event) { KisToolSelectBase::beginPrimaryAction(event); KisPaintDeviceSP dev; if (!currentNode() || !(dev = currentNode()->projection()) || !currentNode()->visible() || !selectionEditable()) { event->ignore(); return; } QPointF pos = convertToPixelCoord(event); KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); QApplication::setOverrideCursor(KisCursor::waitCursor()); KoColor c; dev->pixel(pos.x(), pos.y(), &c); // XXX we should make this configurable: "allow to select transparent" // if (opacity > OPACITY_TRANSPARENT) KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); QRect rc; if (dev->colorSpace()->difference(c.data(), dev->defaultPixel().data()) <= m_fuzziness) { rc = image()->bounds(); } else { rc = dev->exactBounds(); } selectByColor(dev, tmpSel, c.data(), m_fuzziness, rc); tmpSel->invalidateOutlineCache(); KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Similar Color")); helper.selectPixelSelection(tmpSel, selectionAction()); QApplication::restoreOverrideCursor(); } void KisToolSelectSimilar::slotSetFuzziness(int fuzziness) { m_fuzziness = fuzziness; m_configGroup.writeEntry("fuzziness", fuzziness); } QWidget* KisToolSelectSimilar::createOptionWidget() { KisToolSelectBase::createOptionWidget(); KisSelectionOptions *selectionWidget = selectionOptionWidget(); selectionWidget->disableAntiAliasSelectionOption(); selectionWidget->disableSelectionModeOption(); QHBoxLayout* fl = new QHBoxLayout(); QLabel * lbl = new QLabel(i18n("Fuzziness: "), selectionWidget); fl->addWidget(lbl); KisSliderSpinBox* input = new KisSliderSpinBox(selectionWidget); input->setObjectName("fuzziness"); input->setRange(0, 200); input->setSingleStep(10); fl->addWidget(input); connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotSetFuzziness(int))); QVBoxLayout* l = dynamic_cast(selectionWidget->layout()); Q_ASSERT(l); l->insertLayout(1, fl); // load setting from config input->setValue(m_configGroup.readEntry("fuzziness", 20)); return selectionWidget; } -void KisToolSelectSimilar::setSelectionAction(int action) +void KisToolSelectSimilar::resetCursorStyle() { - changeSelectionAction(action); -} - -QMenu* KisToolSelectSimilar::popupActionsMenu() -{ - KisCanvas2 * kisCanvas = dynamic_cast(canvas()); - Q_ASSERT(kisCanvas); - - - return KisSelectionToolHelper::getSelectionContextMenu(kisCanvas); + if (selectionAction() == SELECTION_ADD) { + useCursor(KisCursor::load("tool_similar_selection_cursor_add.png", 6, 6)); + } else if (selectionAction() == SELECTION_SUBTRACT) { + useCursor(KisCursor::load("tool_similar_selection_cursor_sub.png", 6, 6)); + } else { + KisToolSelect::resetCursorStyle(); + } } diff --git a/plugins/tools/selectiontools/kis_tool_select_similar.h b/plugins/tools/selectiontools/kis_tool_select_similar.h index 67068260f5..bbe6a8d313 100644 --- a/plugins/tools/selectiontools/kis_tool_select_similar.h +++ b/plugins/tools/selectiontools/kis_tool_select_similar.h @@ -1,76 +1,75 @@ /* * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * 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 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_SELECT_SIMILAR_H_ #define KIS_TOOL_SELECT_SIMILAR_H_ #include #include #include #include "kis_tool_select_base.h" #include /* * Tool to select colors by pointing at a color on the image. */ class KisToolSelectSimilar: public KisToolSelect { Q_OBJECT public: KisToolSelectSimilar(KoCanvasBase * canvas); void beginPrimaryAction(KoPointerEvent *event) override; void paint(QPainter&, const KoViewConverter &) override {} QWidget* createOptionWidget() override; - QMenu* popupActionsMenu() override; + void resetCursorStyle(); - public Q_SLOTS: +public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void slotSetFuzziness(int); - void setSelectionAction(int); protected: using KisToolSelectBase::m_widgetHelper; private: int m_fuzziness; KConfigGroup m_configGroup; }; class KisToolSelectSimilarFactory : public KoToolFactoryBase { public: KisToolSelectSimilarFactory() : KoToolFactoryBase("KisToolSelectSimilar") { setToolTip(i18n("Similar Color Selection Tool")); setSection(TOOL_TYPE_SELECTION); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setIconName(koIconNameCStr("tool_similar_selection")); setPriority(5); } ~KisToolSelectSimilarFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolSelectSimilar(canvas); } }; #endif // KIS_TOOL_SELECT_SIMILAR_H_ diff --git a/plugins/tools/tool_smart_patch/kis_tool_smart_patch.cpp b/plugins/tools/tool_smart_patch/kis_tool_smart_patch.cpp index 656ba388ca..b969bcd7ea 100644 --- a/plugins/tools/tool_smart_patch/kis_tool_smart_patch.cpp +++ b/plugins/tools/tool_smart_patch/kis_tool_smart_patch.cpp @@ -1,271 +1,271 @@ /* * Copyright (c) 2017 Eugene Ingerman * * 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_smart_patch.h" #include "QApplication" #include "QPainterPath" #include #include #include #include "kis_canvas2.h" #include "kis_cursor.h" #include "kis_painter.h" #include "kis_paintop_preset.h" #include "kundo2magicstring.h" #include "kundo2stack.h" -#include "kis_transaction_based_command.h" +#include "commands_new/kis_transaction_based_command.h" #include "kis_transaction.h" #include "kis_processing_applicator.h" #include "kis_datamanager.h" #include "KoColorSpaceRegistry.h" #include "kis_tool_smart_patch_options_widget.h" #include "libs/image/kis_paint_device_debug_utils.h" #include "kis_paint_layer.h" QRect patchImage(KisPaintDeviceSP imageDev, KisPaintDeviceSP maskDev, int radius, int accuracy); class KisToolSmartPatch::InpaintCommand : public KisTransactionBasedCommand { public: InpaintCommand( KisPaintDeviceSP maskDev, KisPaintDeviceSP imageDev, int accuracy, int patchRadius ) : m_maskDev(maskDev), m_imageDev(imageDev), m_accuracy(accuracy), m_patchRadius(patchRadius) {} KUndo2Command* paint() override { KisTransaction transaction(m_imageDev); patchImage(m_imageDev, m_maskDev, m_patchRadius, m_accuracy); return transaction.endAndTake(); } private: KisPaintDeviceSP m_maskDev, m_imageDev; int m_accuracy, m_patchRadius; }; struct KisToolSmartPatch::Private { KisPaintDeviceSP maskDev = nullptr; KisPainter maskDevPainter; float brushRadius = 50.; //initial default. actually read from ui. KisToolSmartPatchOptionsWidget *optionsWidget = nullptr; QRectF oldOutlineRect; QPainterPath brushOutline; }; KisToolSmartPatch::KisToolSmartPatch(KoCanvasBase * canvas) : KisToolPaint(canvas, KisCursor::blankCursor()), m_d(new Private) { setSupportOutline(true); setObjectName("tool_SmartPatch"); m_d->maskDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); m_d->maskDevPainter.begin( m_d->maskDev ); m_d->maskDevPainter.setPaintColor(KoColor(Qt::magenta, m_d->maskDev->colorSpace())); m_d->maskDevPainter.setBackgroundColor(KoColor(Qt::white, m_d->maskDev->colorSpace())); m_d->maskDevPainter.setFillStyle( KisPainter::FillStyleForegroundColor ); } KisToolSmartPatch::~KisToolSmartPatch() { m_d->optionsWidget = nullptr; m_d->maskDevPainter.end(); } void KisToolSmartPatch::activate(ToolActivation activation, const QSet &shapes) { KisToolPaint::activate(activation, shapes); } void KisToolSmartPatch::deactivate() { KisToolPaint::deactivate(); } void KisToolSmartPatch::resetCursorStyle() { KisToolPaint::resetCursorStyle(); } void KisToolSmartPatch::activatePrimaryAction() { setOutlineEnabled(true); KisToolPaint::activatePrimaryAction(); } void KisToolSmartPatch::deactivatePrimaryAction() { setOutlineEnabled(false); KisToolPaint::deactivatePrimaryAction(); } void KisToolSmartPatch::addMaskPath( KoPointerEvent *event ) { QPointF imagePos = currentImage()->documentToPixel(event->point); QPainterPath currentBrushOutline = brushOutline().translated(imagePos); m_d->maskDevPainter.fillPainterPath(currentBrushOutline); canvas()->updateCanvas(currentImage()->pixelToDocument(m_d->maskDev->exactBounds())); } void KisToolSmartPatch::beginPrimaryAction(KoPointerEvent *event) { //we can only apply inpaint operation to paint layer if ( currentNode().isNull() || !currentNode()->inherits("KisPaintLayer") || nodePaintAbility()!=NodePaintAbility::PAINT ) { KisCanvas2 * kiscanvas = static_cast(canvas()); kiscanvas->viewManager()-> showFloatingMessage( i18n("Select a paint layer to use this tool"), QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter); event->ignore(); return; } addMaskPath(event); setMode(KisTool::PAINT_MODE); KisToolPaint::beginPrimaryAction(event); } void KisToolSmartPatch::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); addMaskPath(event); KisToolPaint::continuePrimaryAction(event); } void KisToolSmartPatch::endPrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); addMaskPath(event); KisToolPaint::endPrimaryAction(event); setMode(KisTool::HOVER_MODE); QApplication::setOverrideCursor(KisCursor::waitCursor()); int accuracy = 50; //default accuracy - middle value int patchRadius = 4; //default radius, which works well for most cases tested if (m_d->optionsWidget) { accuracy = m_d->optionsWidget->getAccuracy(); patchRadius = m_d->optionsWidget->getPatchRadius(); } KisProcessingApplicator applicator( image(), currentNode(), KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Smart Patch")); //actual inpaint operation. filling in areas masked by user applicator.applyCommand( new InpaintCommand( KisPainter::convertToAlphaAsAlpha(m_d->maskDev), currentNode()->paintDevice(), accuracy, patchRadius ), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE ); applicator.end(); image()->waitForDone(); QApplication::restoreOverrideCursor(); m_d->maskDev->clear(); } QPainterPath KisToolSmartPatch::brushOutline( void ) { const qreal diameter = m_d->brushRadius; QPainterPath outline; outline.addEllipse(QPointF(0,0), -0.5 * diameter, -0.5 * diameter ); return outline; } QPainterPath KisToolSmartPatch::getBrushOutlinePath(const QPointF &documentPos, const KoPointerEvent *event) { Q_UNUSED(event); QPointF imagePos = currentImage()->documentToPixel(documentPos); QPainterPath path = brushOutline(); return path.translated( imagePos.rx(), imagePos.ry() ); } void KisToolSmartPatch::requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event) { static QPointF lastDocPoint = QPointF(0,0); if( event ) lastDocPoint=outlineDocPoint; m_d->brushRadius = currentPaintOpPreset()->settings()->paintOpSize(); m_d->brushOutline = getBrushOutlinePath(lastDocPoint, event); QRectF outlinePixelRect = m_d->brushOutline.boundingRect(); QRectF outlineDocRect = currentImage()->pixelToDocument(outlinePixelRect); // This adjusted call is needed as we paint with a 3 pixel wide brush and the pen is outside the bounds of the path // Pen uses view coordinates so we have to zoom the document value to match 2 pixel in view coordinates // See BUG 275829 qreal zoomX; qreal zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); qreal xoffset = 2.0/zoomX; qreal yoffset = 2.0/zoomY; if (!outlineDocRect.isEmpty()) { outlineDocRect.adjust(-xoffset,-yoffset,xoffset,yoffset); } if (!m_d->oldOutlineRect.isEmpty()) { canvas()->updateCanvas(m_d->oldOutlineRect); } if (!outlineDocRect.isEmpty()) { canvas()->updateCanvas(outlineDocRect); } m_d->oldOutlineRect = outlineDocRect; } void KisToolSmartPatch::paint(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(converter); painter.save(); painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); painter.setPen(QColor(128, 255, 128)); painter.drawPath(pixelToView(m_d->brushOutline)); painter.restore(); painter.save(); painter.setBrush(Qt::magenta); QImage img = m_d->maskDev->convertToQImage(0); if( !img.size().isEmpty() ){ painter.drawImage(pixelToView(m_d->maskDev->exactBounds()), img); } painter.restore(); } QWidget * KisToolSmartPatch::createOptionWidget() { KisCanvas2 * kiscanvas = dynamic_cast(canvas()); m_d->optionsWidget = new KisToolSmartPatchOptionsWidget(kiscanvas->viewManager()->resourceProvider(), 0); m_d->optionsWidget->setObjectName(toolId() + "option widget"); return m_d->optionsWidget; } diff --git a/plugins/tools/tool_transform2/CMakeLists.txt b/plugins/tools/tool_transform2/CMakeLists.txt index ce61896b4f..e90230c48e 100644 --- a/plugins/tools/tool_transform2/CMakeLists.txt +++ b/plugins/tools/tool_transform2/CMakeLists.txt @@ -1,49 +1,48 @@ if (NOT WIN32 AND NOT APPLE) add_subdirectory(tests) endif() set(kritatooltransform_SOURCES tool_transform.cc tool_transform_args.cc kis_transform_mask_adapter.cpp kis_animated_transform_parameters.cpp - tool_transform_changes_tracker.cpp kis_tool_transform.cc kis_tool_transform_config_widget.cpp kis_transform_strategy_base.cpp kis_warp_transform_strategy.cpp kis_cage_transform_strategy.cpp kis_simplified_action_policy_strategy.cpp kis_liquify_transform_strategy.cpp kis_liquify_paint_helper.cpp kis_liquify_paintop.cpp kis_liquify_properties.cpp kis_free_transform_strategy.cpp kis_free_transform_strategy_gsl_helpers.cpp kis_perspective_transform_strategy.cpp kis_transform_utils.cpp kis_modify_transform_mask_command.cpp strokes/transform_stroke_strategy.cpp kis_transform_args_keyframe_channel.cpp ) qt5_add_resources(kritatooltransform_SOURCES tool_transform.qrc) ki18n_wrap_ui(kritatooltransform_SOURCES wdg_tool_transform.ui) add_library(kritatooltransform MODULE ${kritatooltransform_SOURCES}) generate_export_header(kritatooltransform BASE_NAME kritatooltransform) if (NOT GSL_FOUND) message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Transform Tool will not be able to scale the image with handles. Please install GSL library.") target_link_libraries(kritatooltransform kritaui) else () target_link_libraries(kritatooltransform kritaui ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif () install(TARGETS kritatooltransform DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( FILES KisToolTransform.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions) diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc index 9b742da8ed..a193b4dac0 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform.cc +++ b/plugins/tools/tool_transform2/kis_tool_transform.cc @@ -1,1278 +1,1282 @@ /* * 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 "krita_container_utils.h" #include "kis_layer_utils.h" #include #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; warpAction = new KisAction(i18n("Warp")); liquifyAction = new KisAction(i18n("Liquify")); cageAction = new KisAction(i18n("Cage")); freeTransformAction = new KisAction(i18n("Free")); perspectiveAction = new KisAction(i18n("Perspective")); // extra actions for free transform that are in the tool options mirrorHorizontalAction = new KisAction(i18n("Mirror Horizontal")); mirrorVericalAction = new KisAction(i18n("Mirror Vertical")); rotateNinteyCWAction = new KisAction(i18n("Rotate 90 degrees Clockwise")); rotateNinteyCCWAction = new KisAction(i18n("Rotate 90 degrees CounterClockwise")); applyTransformation = new KisAction(i18n("Apply")); resetTransformation = new KisAction(i18n("Reset")); m_contextMenu.reset(new QMenu()); 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())); + connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)), + this, SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP))); } KisToolTransform::~KisToolTransform() { cancelStroke(); } void KisToolTransform::outlineChanged() { emit freeTransformChanged(); m_canvas->updateCanvas(); } void KisToolTransform::canvasUpdateRequested() { m_canvas->updateCanvas(); } void KisToolTransform::resetCursorStyle() { 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 (!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(); } QMenu* KisToolTransform::popupActionsMenu() { if (m_contextMenu) { m_contextMenu->clear(); // add a quick switch to different transform types m_contextMenu->addAction(freeTransformAction); m_contextMenu->addAction(perspectiveAction); m_contextMenu->addAction(warpAction); m_contextMenu->addAction(cageAction); m_contextMenu->addAction(liquifyAction); // extra options if free transform is selected if (transformMode() == FreeTransformMode) { m_contextMenu->addSeparator(); m_contextMenu->addAction(mirrorHorizontalAction); m_contextMenu->addAction(mirrorVericalAction); m_contextMenu->addAction(rotateNinteyCWAction); m_contextMenu->addAction(rotateNinteyCCWAction); } m_contextMenu->addSeparator(); m_contextMenu->addAction(applyTransformation); m_contextMenu->addAction(resetTransformation); } return m_contextMenu.data(); } 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 (this->mode() != KisTool::PAINT_MODE) { currentStrategy()->hoverActionCommon(event); setFunctionalCursor(); KisTool::mouseMoveEvent(event); return; } } void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event) { KisTool::mouseReleaseEvent(event); } 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; KisNodeList oldTransformedNodes; if (lastCommand && TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, args, &oldRootNode, &oldTransformedNodes) && args->mode() == mode && oldRootNode == currentNode) { KisNodeList perspectiveTransformedNodes = fetchNodesList(mode, currentNode, m_workRecursively); if (KritaUtils::compareListsUnordered(oldTransformedNodes, perspectiveTransformedNodes)) { args->saveContinuedState(); image()->undoAdapter()->undoLastCommand(); // FIXME: can we make it async? image()->waitForDone(); forceRepaintDelayedLayers(oldRootNode); 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, KisNodeSP(), {}); } 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() { if (m_currentArgs.isIdentity()) { cancelStroke(); } else { slotResetTransform(); } } void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode, bool forceReset) { Q_ASSERT(!m_strokeData.strokeId()); 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; } /** * We must ensure that the currently selected subtree * has finished all its updates. */ forceRepaintDelayedLayers(currentNode); ToolTransformArgs fetchedArgs; bool fetchedFromCommand = false; if (!forceReset) { fetchedFromCommand = tryFetchArgsFromCommandAndUndo(&fetchedArgs, mode, currentNode); } if (m_optionsWidget) { m_workRecursively = m_optionsWidget->workRecursively() || !currentNode->paintDevice(); } QList nodesList = fetchNodesList(mode, currentNode, m_workRecursively); if (nodesList.isEmpty()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selected layer cannot be transformed with active transformation mode "), koIcon("object-locked"), 4000, KisFloatingMessage::High); // force-reset transform mode to default initTransformMode(mode); return; } TransformStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode, nodesList, resources->activeSelection(), image().data()); KisPaintDeviceSP previewDevice = strategy->previewDevice(); KisSelectionSP selection = strategy->realSelection(); const 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); // force-reset transform mode to default initTransformMode(mode); return; } m_transaction = TransformTransactionProperties(srcRect, &m_currentArgs, currentNode, nodesList); 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(nodesList); 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()) { transformClearedDevices(); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::SELECTION, m_currentArgs, m_transaction.rootNode())); // root node is used for progress only image()->endStroke(m_strokeData.strokeId()); } else { image()->cancelStroke(m_strokeData.strokeId()); } m_strokeData.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } 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(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } } void KisToolTransform::commitChanges() { if (!m_strokeData.strokeId()) return; - m_changesTracker.commitConfig(m_currentArgs); + m_changesTracker.commitConfig(toQShared(m_currentArgs.clone())); } -void KisToolTransform::slotTrackerChangedConfig() +void KisToolTransform::slotTrackerChangedConfig(KisToolChangesTrackerDataSP status) { + const ToolTransformArgs *newArgs = dynamic_cast(status.data()); + KIS_SAFE_ASSERT_RECOVER_RETURN(newArgs); + + *m_transaction.currentConfig() = *newArgs; + slotUiChangedConfig(); updateOptionWidget(); } QList KisToolTransform::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive) { QList result; auto fetchFunc = [&result, mode, root] (KisNodeSP node) { if (node->isEditable(node == root) && (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) && !node->inherits("KisFileLayer") && (!node->inherits("KisTransformMask") || node == root)) { result << node; } }; if (recursive) { KisLayerUtils::recursiveApplyNodes(root, fetchFunc); } else { fetchFunc(root); } return result; } bool KisToolTransform::clearDevices(const QList &nodes) { bool haveInvisibleNodes = false; Q_FOREACH (KisNodeSP node, nodes) { haveInvisibleNodes |= !node->visible(false); 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::transformClearedDevices() { Q_FOREACH (KisNodeSP node, m_strokeData.clearedNodes()) { KIS_ASSERT_RECOVER_RETURN(node); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::PAINT_DEVICE, m_currentArgs, node)); } } 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())); connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX())); connect(mirrorVericalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY())); connect(rotateNinteyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW())); connect(rotateNinteyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW())); connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType())); connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType())); connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType())); connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType())); connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType())); connect(applyTransformation, SIGNAL(triggered(bool)), this, SLOT(slotApplyTransform())); connect(resetTransformation, SIGNAL(triggered(bool)), this, SLOT(slotResetTransform())); 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 { KisNodeSP root = m_transaction.rootNode() ? m_transaction.rootNode() : image()->root(); cancelStroke(); image()->waitForDone(); forceRepaintDelayedLayers(root); startStroke(savedMode, true); KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform()); } } else { initTransformMode(m_currentArgs.mode()); slotEditingFinished(); } } void KisToolTransform::slotRestartTransform() { if (!m_strokeData.strokeId()) return; KisNodeSP root = m_transaction.rootNode(); KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above ToolTransformArgs savedArgs(m_currentArgs); cancelStroke(); image()->waitForDone(); forceRepaintDelayedLayers(root); startStroke(savedArgs.mode(), true); } void KisToolTransform::forceRepaintDelayedLayers(KisNodeSP root) { KIS_SAFE_ASSERT_RECOVER_RETURN(root); KisLayerUtils::forceAllDelayedNodesUpdate(root); image()->waitForDone(); } void KisToolTransform::slotEditingFinished() { commitChanges(); } void KisToolTransform::slotUpdateToWarpType() { setTransformMode(KisToolTransform::TransformToolMode::WarpTransformMode); } void KisToolTransform::slotUpdateToPerspectiveType() { setTransformMode(KisToolTransform::TransformToolMode::PerspectiveTransformMode); } void KisToolTransform::slotUpdateToFreeTransformType() { setTransformMode(KisToolTransform::TransformToolMode::FreeTransformMode); } void KisToolTransform::slotUpdateToLiquifyType() { setTransformMode(KisToolTransform::TransformToolMode::LiquifyTransformMode); } void KisToolTransform::slotUpdateToCageType() { setTransformMode(KisToolTransform::TransformToolMode::CageTransformMode); } 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 2355b73cb8..4cb8858cf1 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform.h +++ b/plugins/tools/tool_transform2/kis_tool_transform.h @@ -1,378 +1,378 @@ /* * 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 #include #include #include "tool_transform_args.h" -#include "tool_transform_changes_tracker.h" +#include "KisToolChangesTracker.h" #include "kis_tool_transform_config_widget.h" #include "transform_transaction_properties.h" 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); ~KisToolTransform() override; /** * @brief wantsAutoScroll * reimplemented from KoToolBase * there's an issue where autoscrolling with this tool never makes the * stroke end, so we return false here so that users don't get stuck with * the tool. See bug 362659 * @return false */ bool wantsAutoScroll() const override { return false; } QWidget* createOptionWidget() override; void mousePressEvent(KoPointerEvent *e) override; void mouseMoveEvent(KoPointerEvent *e) override; void mouseReleaseEvent(KoPointerEvent *e) override; 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); QMenu* popupActionsMenu() override; void activatePrimaryAction() override; void deactivatePrimaryAction() override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void activateAlternateAction(AlternateAction action) override; void deactivateAlternateAction(AlternateAction action) override; void beginAlternateAction(KoPointerEvent *event, AlternateAction action) override; void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override; void endAlternateAction(KoPointerEvent *event, AlternateAction action) override; void paint(QPainter& gc, const KoViewConverter &converter) override; 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; public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void deactivate() override; // 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: void resetCursorStyle() override; Q_SIGNALS: void transformModeChanged(); void freeTransformChanged(); void warpTransformChanged(); public Q_SLOTS: void requestUndoDuringStroke() override; void requestStrokeEnd() override; void requestStrokeCancellation() override; void canvasUpdateRequested(); void cursorOutlineUpdateRequested(const QPointF &imagePos); // Update the widget according to m_currentArgs void updateOptionWidget(); void resetRotationCenterButtonsRequested(); void imageTooBigRequested(bool value); private: QList fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive); QScopedPointer m_contextMenu; bool clearDevices(const QList &nodes); void transformClearedDevices(); 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(); void forceRepaintDelayedLayers(KisNodeSP root); 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; QPointer m_canvas; TransformTransactionProperties m_transaction; - TransformChangesTracker m_changesTracker; + KisToolChangesTracker m_changesTracker; /// actions for the context click menu KisAction* warpAction; KisAction* liquifyAction; KisAction* cageAction; KisAction* freeTransformAction; KisAction* perspectiveAction; KisAction* applyTransformation; KisAction* resetTransformation; // a few extra context click options if free transform is active KisAction* mirrorHorizontalAction; KisAction* mirrorVericalAction; KisAction* rotateNinteyCWAction; KisAction* rotateNinteyCCWAction; /** * 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 slotTrackerChangedConfig(KisToolChangesTrackerDataSP status); void slotUiChangedConfig(); void slotApplyTransform(); void slotResetTransform(); void slotRestartTransform(); void slotEditingFinished(); // context menu options for updating the transform type // this is to help with discoverability since come people can't find the tool options void slotUpdateToWarpType(); void slotUpdateToPerspectiveType(); void slotUpdateToFreeTransformType(); void slotUpdateToLiquifyType(); void slotUpdateToCageType(); }; 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); } ~KisToolTransformFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolTransform(canvas); } }; #endif // KIS_TOOL_TRANSFORM_H_ diff --git a/plugins/tools/tool_transform2/tool_transform_args.cc b/plugins/tools/tool_transform2/tool_transform_args.cc index 1a2612d47e..7453961e8e 100644 --- a/plugins/tools/tool_transform2/tool_transform_args.cc +++ b/plugins/tools/tool_transform2/tool_transform_args.cc @@ -1,486 +1,491 @@ /* * 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); } +KisToolChangesTrackerData *ToolTransformArgs::clone() const +{ + return new ToolTransformArgs(*this); +} + 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 b04e2ec363..073e15f8eb 100644 --- a/plugins/tools/tool_transform2/tool_transform_args.h +++ b/plugins/tools/tool_transform2/tool_transform_args.h @@ -1,333 +1,335 @@ /* * 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 "KisToolChangesTrackerData.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 +class KRITATOOLTRANSFORM_EXPORT ToolTransformArgs : public KisToolChangesTrackerData { 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); + KisToolChangesTrackerData *clone() const; + /** * 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, 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 setWarpCalculation(KisWarpTransformWorker::WarpCalculation warpCalc) { m_warpCalculation = warpCalc; } inline KisWarpTransformWorker::WarpCalculation warpCalculation() { return m_warpCalculation; } 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; KisWarpTransformWorker::WarpCalculation m_warpCalculation; // DRAW or GRID 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/tool_transform_changes_tracker.cpp b/plugins/tools/tool_transform2/tool_transform_changes_tracker.cpp deleted file mode 100644 index 68a8195002..0000000000 --- a/plugins/tools/tool_transform2/tool_transform_changes_tracker.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 "tool_transform_changes_tracker.h" - - -TransformChangesTracker::TransformChangesTracker(TransformTransactionProperties *transaction) - : m_transaction(transaction) -{ -} - -void TransformChangesTracker::commitConfig(const ToolTransformArgs &config) -{ - m_config.append(config); -} - -void TransformChangesTracker::requestUndo() -{ - if (m_config.size() > 1) { - m_config.removeLast(); - *m_transaction->currentConfig() = m_config.last(); - emit sigConfigChanged(); - } -} - -void TransformChangesTracker::reset() -{ - m_config.clear(); -} - -bool TransformChangesTracker::isEmpty() const -{ - return m_config.isEmpty(); -} diff --git a/plugins/tools/tool_transform2/tool_transform_changes_tracker.h b/plugins/tools/tool_transform2/tool_transform_changes_tracker.h deleted file mode 100644 index c5bf20a147..0000000000 --- a/plugins/tools/tool_transform2/tool_transform_changes_tracker.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 __TOOL_TRANSFORM_CHANGES_TRACKER_H -#define __TOOL_TRANSFORM_CHANGES_TRACKER_H - -#include - -#include "tool_transform_args.h" -#include "transform_transaction_properties.h" - - -/** - * This class is supposed to support undo operations for the Transform - * Tool. Note, that the transform tool is not going to support redo() - * operations, so we do not use QUndoStack here to not complicate the - * structure of classes. - */ -class TransformChangesTracker : public QObject -{ - Q_OBJECT -public: - TransformChangesTracker(TransformTransactionProperties *transaction); - - void commitConfig(const ToolTransformArgs &config); - void requestUndo(); - void reset(); - - bool isEmpty() const; - -Q_SIGNALS: - void sigConfigChanged(); - -private: - QList m_config; - TransformTransactionProperties *m_transaction; -}; - -#endif /* __TOOL_TRANSFORM_CHANGES_TRACKER_H */