diff --git a/krita/krita.action b/krita/krita.action index fe025f8224..e062893251 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -1,2948 +1,2936 @@ General Open Resources Folder Opens a file browser at the location Krita saves resources such as brushes to. Opens a file browser at the location Krita saves resources such as brushes to. Open Resources Folder 0 0 false Cleanup removed files... Cleanup removed files Cleanup removed files 0 0 false C&ascade Cascade Cascade 10 0 false &Tile Tile Tile 10 0 false Create Resource Bundle... Create Resource Bundle Create Resource Bundle 0 0 false Show File Toolbar Show File Toolbar Show File Toolbar false Show color selector Show color selector Show color selector Shift+I false Show MyPaint shade selector Show MyPaint shade selector Show MyPaint shade selector Shift+M false Show minimal shade selector Show minimal shade selector Show minimal shade selector Shift+N false Show color history Show color history Show color history H false Show common colors Show common colors Show common colors U false Show Tool Options Show Tool Options Show Tool Options \ false Show Brush Editor Show Brush Editor Show Brush Editor F5 false Show Brush Presets Show Brush Presets Show Brush Presets F6 false Toggle Tablet Debugger Toggle Tablet Debugger Toggle Tablet Debugger 0 0 Ctrl+Shift+T false Rename Composition... Rename Composition Rename Composition 0 0 false Update Composition Update Composition Update Composition 0 0 false Painting Make brush color lighter Make brush color lighter Make brush color lighter 0 0 L false Make brush color darker Make brush color darker Make brush color darker 0 0 K false Make brush color more saturated Make brush color more saturated Make brush color more saturated false Make brush color more desaturated Make brush color more desaturated Make brush color more desaturated false Shift brush color hue clockwise Shift brush color hue clockwise Shift brush color hue clockwise false Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise false Make brush color more red Make brush color more red Make brush color more red false Make brush color more green Make brush color more green Make brush color more green false Make brush color more blue Make brush color more blue Make brush color more blue false Make brush color more yellow Make brush color more yellow Make brush color more yellow false Increase opacity Increase opacity Increase opacity 0 0 O false Decrease opacity Decrease opacity Decrease opacity 0 0 I false draw-eraser Set eraser mode Set eraser mode Set eraser mode 10000 0 E true view-refresh Reload Original Preset Reload Original Preset Reload Original Preset 10000 false transparency-unlocked Preserve Alpha Preserve Alpha Preserve Alpha 10000 true transform_icons_penPressure Use Pen Pressure Use Pen Pressure Use Pen Pressure 10000 true symmetry-horizontal Horizontal Mirror Tool Horizontal Mirror Tool Horizontal Mirror Tool 10000 true symmetry-vertical Vertical Mirror Tool Vertical Mirror Tool Vertical Mirror Tool 10000 true - - - Paste at cursor - - Paste at cursor - Paste at cursor - 0 - 0 - - false - - &Invert Selection Invert current selection Invert Selection 10000000000 100 Ctrl+Shift+I false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false &Toggle Selection Display Mode Toggle Selection Display Mode Toggle Selection Display Mode 0 0 false Next Favourite Preset Next Favourite Preset Next Favourite Preset , false Previous Favourite Preset Previous Favourite Preset Previous Favourite Preset . false preset-switcher Switch to Previous Preset Switch to Previous Preset Switch to Previous Preset / false Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar true Reset Foreground and Background Color Reset Foreground and Background Color Reset Foreground and Background Color D false Swap Foreground and Background Color Swap Foreground and Background Color Swap Foreground and Background Color X false smoothing-weighted Brush Smoothing: Weighted Brush Smoothing: Weighted Brush Smoothing: Weighted false smoothing-no Brush Smoothing: Disabled Brush Smoothing: Disabled Brush Smoothing: Disabled false smoothing-stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer false Decrease Brush Size Decrease Brush Size Decrease Brush Size 0 0 [ false smoothing-basic Brush Smoothing: Basic Brush Smoothing: Basic Brush Smoothing: Basic false Increase Brush Size Increase Brush Size Increase Brush Size 0 0 ] false Toggle Assistant Toggle Assistant ToggleAssistant Ctrl+Shift+L true Undo Polygon Selection Points Undo Polygon Selection Points Undo Polygon Selection Points Shift+Z false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false Convert &to Shape Convert to Shape Convert to Shape 10000000000 0 false &Select Opaque Select Opaque Select Opaque 100000 100 false &Show Global Selection Mask Shows global selection as a usual selection mask in <interface>Layers</interface> docker Show Global Selection Mask 100000 100 true Filters color-to-alpha &Color to Alpha... Color to Alpha Color to Alpha 10000 0 false &Top Edge Detection Top Edge Detection Top Edge Detection 10000 0 false &Index Colors... Index Colors Index Colors 10000 0 false Emboss Horizontal &Only Emboss Horizontal Only Emboss Horizontal Only 10000 0 false D&odge Dodge Dodge 10000 0 false &Sharpen Sharpen Sharpen 10000 0 false B&urn Burn Burn 10000 0 false &Mean Removal Mean Removal Mean Removal 10000 0 false &Gaussian Blur... Gaussian Blur Gaussian Blur 10000 0 false Emboss &in All Directions Emboss in All Directions Emboss in All Directions 10000 0 false &Small Tiles... Small Tiles Small Tiles 10000 0 false &Levels... Levels Levels 10000 0 Ctrl+L false &Sobel... Sobel Sobel 10000 0 false &Wave... Wave Wave 10000 0 false &Motion Blur... Motion Blur Motion Blur 10000 0 false &Color Adjustment curves... Color Adjustment curves Color Adjustment curves 10000 0 Ctrl+M false Pi&xelize... Pixelize Pixelize 10000 0 false Emboss (&Laplacian) Emboss (Laplacian) Emboss (Laplacian) 10000 0 false &Left Edge Detection Left Edge Detection Left Edge Detection 10000 0 false &Blur... Blur Blur 10000 0 false &Raindrops... Raindrops Raindrops 10000 0 false &Bottom Edge Detection Bottom Edge Detection Bottom Edge Detection 10000 0 false &Random Noise... Random Noise Random Noise 10000 0 false &Brightness/Contrast curve... Brightness/Contrast curve Brightness/Contrast curve 10000 0 false Colo&r Balance.. Color Balance.. Color Balance.. 10000 0 Ctrl+B false &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 References References References false Rectangle Tool Rectangle Tool Rectangle Tool false Multibrush Tool Multibrush Tool Multibrush Tool Q false Shape Manipulation Tool Shape Manipulation Tool Shape Manipulation Tool false Color Picker Select a color from the image or current layer Select a color from the image or current layer P false Text Editing Tool Text editing Text editing false Outline Selection Tool Outline Selection Tool Outline Selection Tool false Artistic Text Tool Artistic text editing Artistic text editing false Bezier Curve Selection Tool Select a Bezier Curve Selection Tool false Similar Color Selection Tool Select a Similar Color Selection Tool false Fill Tool Fill a contiguous area of color with a color, or fill a selection. Fill a contiguous area of color with a color, or fill a selection. F false Line Tool Line Tool Line Tool false Freehand Path Tool Freehand Path Tool Freehand Path Tool false Bezier Curve Tool Bezier Curve Tool. Shift-mouseclick ends the curve. Bezier Curve Tool. Shift-mouseclick ends the curve. false Ellipse Tool Ellipse Tool Ellipse Tool false Freehand Brush Tool Freehand Brush Tool Freehand Brush Tool B false Create object Create object Create object false Elliptical Selection Tool Elliptical Selection Tool Elliptical Selection Tool J false Contiguous Selection Tool Contiguous Selection Tool Contiguous Selection Tool false Pattern editing Pattern editing Pattern editing false Review Review Review false Draw a gradient. Draw a gradient. Draw a gradient. G false Polygonal Selection Tool Polygonal Selection Tool Polygonal Selection Tool false Measurement Tool Measure the distance between two points Measure the distance between two points false Rectangular Selection Tool Rectangular Selection Tool Rectangular Selection Tool Ctrl+R false Move Tool Move a layer Move a layer T false Vector Image Tool Vector Image (EMF/WMF/SVM/SVG) tool Vector Image (EMF/WMF/SVM/SVG) tool false Calligraphy Calligraphy Calligraphy false Path editing Path editing Path editing false Polyline Tool Polyline Tool. Shift-mouseclick ends the polyline. Polyline Tool. Shift-mouseclick ends the polyline. false Transform Tool Transform a layer or a selection Transform a layer or a selection Ctrl+T false Ruler assistant editor tool Ruler assistant editor tool Ruler assistant editor tool false Text tool Text tool Text tool false Gradient Editing Tool Gradient editing Gradient editing false Blending Modes Select Normal Blending Mode Select Normal Blending Mode Select Normal Blending Mode 0 0 Alt+Shift+N false Select Dissolve Blending Mode Select Dissolve Blending Mode Select Dissolve Blending Mode 0 0 Alt+Shift+I false Select Behind Blending Mode Select Behind Blending Mode Select Behind Blending Mode 0 0 Alt+Shift+Q false Select Clear Blending Mode Select Clear Blending Mode Select Clear Blending Mode 0 0 Alt+Shift+R false Select Darken Blending Mode Select Darken Blending Mode Select Darken Blending Mode 0 0 Alt+Shift+K false Select Multiply Blending Mode Select Multiply Blending Mode Select Multiply Blending Mode 0 0 Alt+Shift+M false Select Color Burn Blending Mode Select Color Burn Blending Mode Select Color Burn Blending Mode 0 0 Alt+Shift+B false Select Linear Burn Blending Mode Select Linear Burn Blending Mode Select Linear Burn Blending Mode 0 0 Alt+Shift+A false Select Lighten Blending Mode Select Lighten Blending Mode Select Lighten Blending Mode 0 0 Alt+Shift+G false Select Screen Blending Mode Select Screen Blending Mode Select Screen Blending Mode 0 0 Alt+Shift+S false Select Color Dodge Blending Mode Select Color Dodge Blending Mode Select Color Dodge Blending Mode 0 0 Alt+Shift+D false Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode 0 0 Alt+Shift+W false Select Overlay Blending Mode Select Overlay Blending Mode Select Overlay Blending Mode 0 0 Alt+Shift+O false Select Soft Light Blending Mode Select Soft Light Blending Mode Select Soft Light Blending Mode 0 0 Alt+Shift+F false Select Hard Light Blending Mode Select Hard Light Blending Mode Select Hard Light Blending Mode 0 0 Alt+Shift+H false Select Vivid Light Blending Mode Select Vivid Light Blending Mode Select Vivid Light Blending Mode 0 0 Alt+Shift+V false Select Linear Light Blending Mode Select Linear Light Blending Mode Select Linear Light Blending Mode 0 0 Alt+Shift+J false Select Pin Light Blending Mode Select Pin Light Blending Mode Select Pin Light Blending Mode 0 0 Alt+Shift+Z false Select Hard Mix Blending Mode Select Hard Mix Blending Mode Select Hard Mix Blending Mode 0 0 Alt+Shift+L false Select Difference Blending Mode Select Difference Blending Mode Select Difference Blending Mode 0 0 Alt+Shift+E false Select Exclusion Blending Mode Select Exclusion Blending Mode Select Exclusion Blending Mode 0 0 Alt+Shift+X false Select Hue Blending Mode Select Hue Blending Mode Select Hue Blending Mode 0 0 Alt+Shift+U false Select Saturation Blending Mode Select Saturation Blending Mode Select Saturation Blending Mode 0 0 Alt+Shift+T false Select Color Blending Mode Select Color Blending Mode Select Color Blending Mode 0 0 Alt+Shift+C false Select Luminosity Blending Mode Select Luminosity Blending Mode Select Luminosity Blending Mode 0 0 Alt+Shift+Y false Animation Previous frame Move to previous frame Move to previous frame 1 0 false Next frame Move to next frame Move to next frame 1 0 false Play / pause animation Play / pause animation Play / pause animation 1 0 false Add blank frame Add blank frame Add blank frame 100000 0 false Copy Frame Add duplicate frame Add duplicate frame 100000 0 false Toggle onion skin Toggle onion skin Toggle onion skin 100000 0 false Previous Keyframe false Next Keyframe false First Frame false Last Frame false Auto Frame Mode true true Add blank frame Add blank frame Add blank frame 100000 0 false Show in Timeline true Layers Activate next layer Activate next layer Activate next layer 1000 0 PgUp false Activate previous layer Activate previous layer Activate previous layer 1000 0 PgDown false Activate previously selected layer Activate previously selected layer Activate previously selected layer 1000 0 ; false groupLayer &Group Layer Group Layer Group Layer 1000 0 false cloneLayer &Clone Layer Clone Layer Clone Layer 1000 0 false vectorLayer &Vector Layer Vector Layer Vector Layer 1000 0 false filterLayer &Filter Layer... Filter Layer Filter Layer 1000 0 false fillLayer &Fill Layer... Fill Layer Fill Layer 1000 0 false fileLayer &File Layer... File Layer File Layer 1000 0 false transparencyMask &Transparency Mask Transparency Mask Transparency Mask 100000 0 false filterMask &Filter Mask... Filter Mask Filter Mask 100000 0 false filterMask &Colorize Mask Colorize Mask Colorize Mask 100000 0 false transformMask &Transform Mask... Transform Mask Transform Mask 100000 0 false selectionMask &Local Selection Local Selection Local Selection 100000 0 false view-filter &Isolate Layer Isolate Layer Isolate Layer 1000 0 true paintLayer &Paint Layer Paint Layer Paint Layer 1000 0 Insert false &New Layer From Visible New layer from visible New layer from visible 1000 0 false duplicatelayer &Duplicate Layer or Mask Duplicate Layer or Mask Duplicate Layer or Mask 1000 0 Ctrl+J false &Cut Selection to New Layer Cut Selection to New Layer Cut Selection to New Layer 100000000 1 Ctrl+Shift+J false Copy &Selection to New Layer Copy Selection to New Layer Copy Selection to New Layer 100000000 0 Ctrl+Alt+J false Copy Layer Copy layer to clipboard Copy layer to clipboard 10000 0 false Cut Layer Cut layer to clipboard Cut layer to clipboard 10000 0 false Paste Layer Paste layer from clipboard Paste layer from clipboard 10000 0 false Quick Group Create a group layer containing selected layers Quick Group 100000 0 Ctrl+G false Quick Ungroup Remove grouping of the layers or remove one layer out of the group Quick Ungroup 100000 0 Ctrl+Alt+G false Quick Clipping Group Group selected layers and add a layer with clipped alpha channel Quick Clipping Group 100000 0 Ctrl+Shift+G false All Layers Select all layers Select all layers 10000 0 false Visible Layers Select all visible layers Select all visible layers 10000 0 false Locked Layers Select all locked layers Select all locked layers 10000 0 false Invisible Layers Select all invisible layers Select all invisible layers 10000 0 false Unlocked Layers Select all unlocked layers Select all unlocked layers 10000 0 false document-save &Save Layer/Mask... Save Layer/Mask Save Layer/Mask 1000 0 false document-save Save &Group Layers... Save Group Layers Save Group Layers 100000 0 false Convert group to &animated layer Convert child layers into animation frames Convert child layers into animation frames 100000 0 false I&mport Layer... Import Layer Import Layer 100000 0 false paintLayer &as Paint Layer... as Paint Layer as Paint Layer 1000 0 false transparencyMask as &Transparency Mask... as Transparency Mask as Transparency Mask 1000 0 false filterMask as &Filter Mask... as Filter Mask as Filter Mask 1000 0 false selectionMask as &Selection Mask... as Selection Mask as Selection Mask 1000 0 false paintLayer to &Paint Layer to Paint Layer to Paint Layer 1000 0 false transparencyMask to &Transparency Mask to Transparency Mask to Transparency Mask 1000 0 false filterMask to &Filter Mask... to Filter Mask to Filter Mask 1000 0 false selectionMask to &Selection Mask to Selection Mask to Selection Mask 1000 0 false transparencyMask &Alpha into Mask Alpha into Mask Alpha into Mask 100000 10 false transparency-enabled &Write as Alpha Write as Alpha Write as Alpha 1000000 1 false document-save &Save Merged... Save Merged Save Merged 1000000 0 false split-layer Split Layer... Split Layer Split Layer 1000 0 false Wavelet Decompose ... Wavelet Decompose Wavelet Decompose 1000 1 false symmetry-horizontal Mirror Layer Hori&zontally Mirror Layer Horizontally Mirror Layer Horizontally 1000 1 false symmetry-vertical Mirror Layer &Vertically Mirror Layer Vertically Mirror Layer Vertically 1000 1 false &Rotate Layer... Rotate Layer Rotate Layer 100000 1 false object-rotate-right Rotate &Layer 90° to the Right Rotate Layer 90° to the Right Rotate Layer 90° to the Right 100000 1 false object-rotate-left Rotate Layer &90° to the Left Rotate Layer 90° to the Left Rotate Layer 90° to the Left 100000 1 false Rotate Layer &180° Rotate Layer 180° Rotate Layer 180° 100000 1 false Scale &Layer to new Size... Scale Layer to new Size Scale Layer to new Size 100000 1 false &Shear Layer... Shear Layer Shear Layer 100000 1 false &Offset Layer... Offset Layer Offset Layer 100000 1 false Clones &Array... Clones Array Clones Array 100000 0 false &Edit metadata... Edit metadata Edit metadata 100000 1 false &Histogram... Histogram Histogram 100000 0 false &Convert Layer Color Space... Convert Layer Color Space Convert Layer Color Space 100000 1 false merge-layer-below &Merge with Layer Below Merge with Layer Below Merge with Layer Below 100000 0 Ctrl+E false &Flatten Layer Flatten Layer Flatten Layer 100000 0 false Ras&terize Layer Rasterize Layer Rasterize Layer 10000000 1 false Flatten ima&ge Flatten image Flatten image 100000 0 Ctrl+Shift+E false La&yer Style... Layer Style Layer Style 100000 1 false Move into previous group Move into previous group Move into previous group 0 0 false Move into next group Move into next group Move into next group 0 0 false Rename current layer Rename current layer Rename current layer 100000 0 F2 false deletelayer &Remove Layer Remove Layer Remove Layer 1000 1 Shift+Delete false arrowupblr Move Layer or Mask Up Move Layer or Mask Up Ctrl+PgUp false arrowdown Move Layer or Mask Down Move Layer or Mask Down Ctrl+PgDown false properties &Properties... Properties Properties 1000 1 F3 false diff --git a/krita/krita.xmlgui b/krita/krita.xmlgui index 13afe46a44..18f97d110c 100644 --- a/krita/krita.xmlgui +++ b/krita/krita.xmlgui @@ -1,374 +1,375 @@ &File &Edit + &View &Canvas &Snap To &Image &Rotate &Layer New &Import/Export Import &Convert &Select &Group &Transform &Rotate S&plit S&plit Alpha &Select Filte&r &Tools Recording Macros Setti&ngs &Help File Brushes and Stuff diff --git a/krita/kritamenu.action b/krita/kritamenu.action index 3ca097eb1f..85266fe675 100644 --- a/krita/kritamenu.action +++ b/krita/kritamenu.action @@ -1,1720 +1,1732 @@ File document-new &New Create new document New 0 0 Ctrl+N false document-open &Open... Open an existing document Open 0 0 Ctrl+O false document-open-recent Open &Recent Open a document which was recently opened Open Recent 1 0 false document-save &Save Save Save 1 0 Ctrl+S false document-save-as Save &As... Save document under a new name Save As 1 0 Ctrl+Shift+S false document-import Open ex&isting Document as Untitled Document... Open existing Document as Untitled Document Open existing Document as Untitled Document 0 0 false document-export E&xport... Export Export 1 0 false application-pdf &Export as PDF... Export as PDF Export as PDF 1 0 false Import animation frames... Import animation frames Import animation frames 1 0 false &Render Animation... Render Animation to GIF, Image Sequence or Video Render Animation 1000 0 false &Render Image Sequence Again Render Animation to Image Sequence Again Render Animation 1000 0 false Save Incremental &Version Save Incremental Version Save Incremental Version 1 0 Ctrl+Alt+S false Save Incremental &Backup Save Incremental Backup Save Incremental Backup 1 0 F4 false &Create Template From Image... Create Template From Image Create Template From Image 1 0 false Create Copy &From Current Image Create Copy From Current Image Create Copy From Current Image 1 0 false document-print &Print... Print document Print 1 0 Ctrl+P false document-print-preview Print Previe&w Show a print preview of document Print Preview 1 0 false configure &Document Information Document Information Document Information 1 0 false &Close All Close All Close All 1 0 Ctrl+Shift+W false C&lose Close Close 1 0 false &Quit Quit application Quit 0 0 Ctrl+Q false Edit edit-undo Undo Undo last action Undo 1 0 Ctrl+Z false edit-redo Redo Redo last undone action Redo 1 0 Ctrl+Shift+Z false edit-cut Cu&t Cut selection to clipboard Cut 0 0 Ctrl+X false edit-copy &Copy Copy selection to clipboard Copy 0 0 Ctrl+C false C&opy (sharp) Copy (sharp) Copy (sharp) 100000000 0 false Cut (&sharp) Cut (sharp) Cut (sharp) 100000000 0 false Copy &merged Copy merged Copy merged 100000000 0 Ctrl+Shift+C false edit-paste &Paste Paste clipboard content Paste 0 0 Ctrl+V false + + + Paste 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 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 view-grid Show &Grid Show Grid Show Grid 1000 0 Ctrl+Shift+' true Snap To Grid Snap To Grid Snap To Grid 1000 Ctrl+Shift+; true Show Snap Options Popup Show Snap Options Popup Show Snap Options Popup 1000 Shift+s false Snap Orthogonal Snap Orthogonal Snap Orthogonal 1000 true Snap Node Snap Node Snap Node 1000 true Snap Extension Snap Extension Snap Extension 1000 true Snap Intersection Snap Intersection Snap Intersection 1000 true Snap Bounding Box Snap Bounding Box Snap Bounding Box 1000 true Snap Image Bounds Snap Image Bounds Snap Image Bounds 1000 true Snap Image Center Snap Image Center Snap Image Center 1000 true S&how Painting Assistants Show Painting Assistants Show Painting Assistants 1000 0 true Show &Assistant Previews Show Assistant Previews Show Assistant Previews 1000 0 true Image document-properties &Properties... Properties Properties 1000 0 false format-stroke-color &Image Background Color and Transparency... Change the background color of the image Image Background Color and Transparency 1000 0 false &Convert Image Color Space... Convert Image Color Space Convert Image Color Space 1000 0 false trim-to-image &Trim to Image Size Trim to Image Size Trim to Image Size 1 0 false Trim to Current &Layer Trim to Current Layer Trim to Current Layer 100000 0 false Trim to S&election Trim to Selection Trim to Selection 100000000 0 false &Rotate Image... Rotate Image Rotate Image 1000 0 false object-rotate-right Rotate &Image 90° to the Right Rotate Image 90° to the Right Rotate Image 90° to the Right 1000 0 false object-rotate-left Rotate Image &90° to the Left Rotate Image 90° to the Left Rotate Image 90° to the Left 1000 0 false Rotate Image &180° Rotate Image 180° Rotate Image 180° 1000 0 false &Shear Image... Shear Image Shear Image 1000 0 false symmetry-horizontal &Mirror Image Horizontally Mirror Image Horizontally Mirror Image Horizontally 1000 0 false symmetry-vertical Mirror Image &Vertically Mirror Image Vertically Mirror Image Vertically 1000 0 false Scale Image To &New Size... Scale Image To New Size Scale Image To New Size 1000 0 Ctrl+Alt+I false &Offset Image... Offset Image Offset Image 1000 0 false R&esize Canvas... Resize Canvas Resize Canvas 1000 0 Ctrl+Alt+C false Im&age Split Image Split Image Split 1000 0 false Separate Ima&ge... Separate Image Separate Image 1000 0 false Select edit-select-all Select &All Select All Select All 0 0 Ctrl+A false edit-select-all &Deselect Deselect Deselect 1100000000 0 Ctrl+Shift+A false &Reselect Reselect Reselect 0 0 Ctrl+Shift+D false &Invert Invert Invert 10000 0 Ctrl+I false &Convert to Vector Selection Convert to Vector Selection Convert to Vector Selection 10000000000 0 false Convert Shapes to &Vector Selection Convert Shapes to Vector Selection Convert Shapes to Vector Selection 1000000000 0 false &Feather Selection... Feather Selection Feather Selection 10000000000 100 Shift+F6 false Dis&play Selection Display Selection Display Selection 1000 0 Ctrl+H true Sca&le... Scale Scale 100000000 100 false S&elect from Color Range... Select from Color Range Select from Color Range 10000 100 false Select &Opaque Select Opaque Select Opaque 10000 100 false &Grow Selection... Grow Selection Grow Selection 10000000000 100 false S&hrink Selection... Shrink Selection Shrink Selection 10000000000 100 false &Border Selection... Border Selection Border Selection 10000000000 100 false S&mooth Smooth Smooth 10000000000 100 false Filter &Apply Filter Again Apply Filter Again Apply Filter Again 0 0 Ctrl+F false Adjust Adjust Adjust false Artistic Artistic Artistic false Blur Blur Blur false Colors Colors Colors false Edge Detection Edge Detection Edge Detection false Enhance Enhance Enhance false Emboss Emboss Emboss false Map Map Map false Other Other Other false gmic G'MIC Apply G'Mic Action Apply G'Mic Action false Tools media-record &Start recording macro Start recording macro Start recording macro 1000 0 false media-playback-stop Stop &recording actions Stop recording actions Stop recording actions 1000 0 false media-playback-start &Open and play... Open and play Open and play 0 0 false document-edit Open &and edit... Open and edit Open and edit 0 0 false Settings configure &Configure Krita... Configure Krita Configure Krita 0 0 false &Manage Resources... Manage Resources Manage Resources 0 0 false preferences-desktop-locale Switch Application &Language... Switch Application Language Switch Application Language false &Show Dockers Show Dockers Show Dockers 0 0 true Sho&w Docker Titlebars Show Docker Titlebars Show Docker Titlebars 0 0 true configure Configure Tool&bars... Configure Toolbars Configure Toolbars 0 0 false Dockers Dockers Dockers false &Themes Themes Themes false im-user Active Author Profile Active Author Profile Active Author Profile configure-shortcuts Configure S&hortcuts... Configure Shortcuts Configure Shortcuts 0 0 false &Window Window Window false Help help-contents Krita &Handbook Krita Handbook Krita Handbook F1 false tools-report-bug &Report Bug... Report Bug Report Bug false calligrakrita &About Krita About Krita About Krita false kde About &KDE About KDE About KDE false Brushes and Stuff &Gradients Gradients Gradients false &Patterns Patterns Patterns false &Color Color Color false &Painter's Tools Painter's Tools Painter's Tools false Brush composite Brush composite Brush composite false Brush option slider 1 Brush option slider 1 Brush option slider 1 false Brush option slider 2 Brush option slider 2 Brush option slider 2 false Brush option slider 3 Brush option slider 3 Brush option slider 3 false Mirror Mirror Mirror false Workspaces Workspaces Workspaces false diff --git a/libs/flake/CMakeLists.txt b/libs/flake/CMakeLists.txt index 8cf05f904f..5b77141235 100644 --- a/libs/flake/CMakeLists.txt +++ b/libs/flake/CMakeLists.txt @@ -1,228 +1,229 @@ project(kritaflake) include_directories( ${CMAKE_SOURCE_DIR}/libs/flake/commands ${CMAKE_SOURCE_DIR}/libs/flake/tools ${CMAKE_SOURCE_DIR}/libs/flake/svg ${CMAKE_BINARY_DIR}/libs/flake ) add_subdirectory(styles) add_subdirectory(tests) set(kritaflake_SRCS KoGradientHelper.cpp KoFlake.cpp KoCanvasBase.cpp KoResourceManager_p.cpp KoDerivedResourceConverter.cpp KoResourceUpdateMediator.cpp KoCanvasResourceManager.cpp KoDocumentResourceManager.cpp KoCanvasObserverBase.cpp KoCanvasSupervisor.cpp KoDockFactoryBase.cpp KoDockRegistry.cpp KoDataCenterBase.cpp KoInsets.cpp KoPathShape.cpp KoPathPoint.cpp KoPathSegment.cpp KoSelection.cpp KoSelectedShapesProxy.cpp KoSelectedShapesProxySimple.cpp KoShape.cpp KoShapeAnchor.cpp KoShapeBasedDocumentBase.cpp KoShapeApplicationData.cpp KoShapeContainer.cpp KoShapeContainerModel.cpp KoShapeGroup.cpp KoShapeManager.cpp KoShapePaintingContext.cpp KoFrameShape.cpp KoUnavailShape.cpp KoMarker.cpp KoMarkerCollection.cpp KoToolBase.cpp KoCanvasController.cpp KoCanvasControllerWidget.cpp KoCanvasControllerWidgetViewport_p.cpp KoShapeRegistry.cpp KoDeferredShapeFactoryBase.cpp KoToolFactoryBase.cpp KoPathShapeFactory.cpp KoShapeFactoryBase.cpp KoShapeUserData.cpp KoParameterShape.cpp KoPointerEvent.cpp KoShapeController.cpp KoToolSelection.cpp KoShapeLayer.cpp KoPostscriptPaintDevice.cpp KoInputDevice.cpp KoToolManager_p.cpp KoToolManager.cpp KoToolRegistry.cpp KoToolProxy.cpp KoShapeSavingContext.cpp KoShapeLoadingContext.cpp KoLoadingShapeUpdater.cpp KoPathShapeLoader.cpp KoShapeStrokeModel.cpp KoShapeStroke.cpp KoShapeBackground.cpp KoColorBackground.cpp KoGradientBackground.cpp KoOdfGradientBackground.cpp KoHatchBackground.cpp KoPatternBackground.cpp KoVectorPatternBackground.cpp KoShapeConfigWidgetBase.cpp KoDrag.cpp + KoSvgPaste.cpp KoDragOdfSaveHelper.cpp KoShapeOdfSaveHelper.cpp KoShapePaste.cpp KoConnectionPoint.cpp KoConnectionShape.cpp KoConnectionShapeLoadingUpdater.cpp KoConnectionShapeFactory.cpp KoConnectionShapeConfigWidget.cpp KoSnapGuide.cpp KoSnapProxy.cpp KoSnapStrategy.cpp KoSnapData.cpp KoShapeShadow.cpp KoSharedLoadingData.cpp KoSharedSavingData.cpp KoViewConverter.cpp KoInputDeviceHandler.cpp KoInputDeviceHandlerEvent.cpp KoInputDeviceHandlerRegistry.cpp KoImageData.cpp KoImageData_p.cpp KoImageCollection.cpp KoOdfWorkaround.cpp KoFilterEffect.cpp KoFilterEffectStack.cpp KoFilterEffectFactoryBase.cpp KoFilterEffectRegistry.cpp KoFilterEffectConfigWidgetBase.cpp KoFilterEffectRenderContext.cpp KoFilterEffectLoadingContext.cpp KoTextShapeDataBase.cpp KoTosContainer.cpp KoTosContainerModel.cpp KoClipPath.cpp KoClipMask.cpp KoClipMaskPainter.cpp KoCurveFit.cpp commands/KoShapeGroupCommand.cpp commands/KoShapeAlignCommand.cpp commands/KoShapeBackgroundCommand.cpp commands/KoShapeCreateCommand.cpp commands/KoShapeDeleteCommand.cpp commands/KoShapeDistributeCommand.cpp commands/KoShapeLockCommand.cpp commands/KoShapeMoveCommand.cpp commands/KoShapeResizeCommand.cpp commands/KoShapeShearCommand.cpp commands/KoShapeSizeCommand.cpp commands/KoShapeStrokeCommand.cpp commands/KoShapeUngroupCommand.cpp commands/KoShapeReorderCommand.cpp commands/KoShapeKeepAspectRatioCommand.cpp commands/KoPathBaseCommand.cpp commands/KoPathPointMoveCommand.cpp commands/KoPathControlPointMoveCommand.cpp commands/KoPathPointTypeCommand.cpp commands/KoPathPointRemoveCommand.cpp commands/KoPathPointInsertCommand.cpp commands/KoPathSegmentBreakCommand.cpp commands/KoPathBreakAtPointCommand.cpp commands/KoPathSegmentTypeCommand.cpp commands/KoPathCombineCommand.cpp commands/KoSubpathRemoveCommand.cpp commands/KoSubpathJoinCommand.cpp commands/KoParameterHandleMoveCommand.cpp commands/KoParameterToPathCommand.cpp commands/KoShapeTransformCommand.cpp commands/KoPathFillRuleCommand.cpp commands/KoConnectionShapeTypeCommand.cpp commands/KoShapeShadowCommand.cpp commands/KoPathReverseCommand.cpp commands/KoShapeRenameCommand.cpp commands/KoShapeRunAroundCommand.cpp commands/KoPathPointMergeCommand.cpp commands/KoShapeTransparencyCommand.cpp commands/KoShapeClipCommand.cpp commands/KoShapeUnclipCommand.cpp commands/KoPathShapeMarkerCommand.cpp commands/KoShapeConnectionChangeCommand.cpp tools/KoCreateShapeStrategy.cpp tools/KoPathToolFactory.cpp tools/KoPathTool.cpp tools/KoPathToolSelection.cpp tools/KoPathToolHandle.cpp tools/PathToolOptionWidget.cpp tools/KoPathPointRubberSelectStrategy.cpp tools/KoPathPointMoveStrategy.cpp tools/KoPathConnectionPointStrategy.cpp tools/KoPathControlPointMoveStrategy.cpp tools/KoParameterChangeStrategy.cpp tools/KoZoomTool.cpp tools/KoZoomToolFactory.cpp tools/KoZoomToolWidget.cpp tools/KoZoomStrategy.cpp tools/KoPanTool.cpp tools/KoPanToolFactory.cpp tools/KoInteractionTool.cpp tools/KoInteractionStrategy.cpp tools/KoInteractionStrategyFactory.cpp tools/KoCreateShapesTool.cpp tools/KoCreateShapesToolFactory.cpp tools/KoShapeRubberSelectStrategy.cpp tools/KoPathSegmentChangeStrategy.cpp svg/KoShapePainter.cpp svg/SvgUtil.cpp svg/SvgGraphicContext.cpp svg/SvgSavingContext.cpp svg/SvgWriter.cpp svg/SvgStyleWriter.cpp svg/SvgShape.cpp svg/SvgParser.cpp svg/SvgStyleParser.cpp svg/SvgGradientHelper.cpp svg/SvgFilterHelper.cpp svg/SvgCssHelper.cpp svg/SvgClipPathHelper.cpp svg/SvgLoadingContext.cpp svg/SvgShapeFactory.cpp svg/parsers/SvgTransformParser.cpp FlakeDebug.cpp ) ki18n_wrap_ui(kritaflake_SRCS tools/PathToolOptionWidgetBase.ui KoConnectionShapeConfigWidget.ui tools/KoZoomToolWidget.ui ) add_library(kritaflake SHARED ${kritaflake_SRCS}) generate_export_header(kritaflake BASE_NAME kritaflake) target_include_directories(kritaflake PUBLIC $ $ $ ) target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritacommand KF5::WidgetsAddons Qt5::Svg) set_target_properties(kritaflake PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaflake ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/flake/KoCanvasController.h b/libs/flake/KoCanvasController.h index e1ab05fb1a..297650c300 100644 --- a/libs/flake/KoCanvasController.h +++ b/libs/flake/KoCanvasController.h @@ -1,471 +1,478 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2008 Thomas Zander * Copyright (C) 2007-2010 Boudewijn Rempt * Copyright (C) 2007-2008 C. Boemann * Copyright (C) 2006-2007 Jan Hambrecht * Copyright (C) 2009 Thorsten Zachmann * * 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 KOCANVASCONTROLLER_H #define KOCANVASCONTROLLER_H #include "kritaflake_export.h" #include #include #include #include class KActionCollection; class QRect; class QRectF; class KoShape; class KoCanvasBase; class KoCanvasControllerProxyObject; /** * KoCanvasController is the base class for wrappers around your canvas * that provides scrolling and zooming for your canvas. * * Flake does not provide a canvas, the application will have to * implement a canvas themselves. You canvas can be QWidget-based * or something we haven't invented yet -- as long the class that holds the canvas * imlements KoCanvasController, tools, scrolling and zooming will work. * * A KoCanvasController implementation acts as a decorator around the canvas widget * and provides a way to scroll the canvas, allows the canvas to be centered * in the viewArea and manages tool activation. * *

The using application can instantiate this class and add its * canvas using the setCanvas() call. Which is designed so it can be * called multiple times if you need to exchange one canvas * widget for another, for instance, switching between a plain QWidget or a QOpenGLWidget. * *

There is _one_ KoCanvasController per canvas in your * application. * *

The canvas widget is at most as big as the viewport of the scroll * area, and when the view on the document is near its edges, smaller. * In your canvas widget code, you can find the right place in your * document in view coordinates (pixels) by adding the documentOffset */ class KRITAFLAKE_EXPORT KoCanvasController { public: /// An enum to alter the positioning and size of the canvas inside the canvas controller enum CanvasMode { AlignTop, ///< canvas is top aligned if smaller than the viewport Centered, ///< canvas is centered if smaller than the viewport Infinite, ///< canvas is never smaller than the viewport Spreadsheet ///< same as Infinite, but supports right-to-left layouts }; // proxy QObject: use this to connect to slots and signals. KoCanvasControllerProxyObject *proxyObject; /** * Constructor. * @param actionCollection the action collection for this canvas */ explicit KoCanvasController(KActionCollection* actionCollection); virtual ~KoCanvasController(); public: /** * Returns the current margin that is used to pad the canvas with. * This value is read from the KConfig property "canvasmargin" */ virtual int margin() const; /** * Set the new margin to pad the canvas with. */ virtual void setMargin(int margin); /** * Sets the how the canvas behaves if the zoomed document becomes smaller than the viewport. * @param mode the new canvas mode, CanvasMode::Centered is the default value */ virtual void setCanvasMode(KoCanvasController::CanvasMode mode); /// Returns the current canvas mode virtual KoCanvasController::CanvasMode canvasMode() const; /** * compatibility with QAbstractScrollArea */ virtual void scrollContentsBy(int dx, int dy) = 0; /** * @return the size of the viewport */ virtual QSize viewportSize() const = 0; /** * Set the shadow option -- by default the canvas controller draws * a black shadow around the canvas widget, which you may or may * not want. * * @param drawShadow if true, the shadow is drawn, if false, not */ virtual void setDrawShadow(bool drawShadow) = 0; /** * Set the new canvas to be shown as a child * Calling this will emit canvasRemoved() if there was a canvas before, and will emit * canvasSet() with the new canvas. * @param canvas the new canvas. The KoCanvasBase::canvas() will be called to retrieve the * actual widget which will then be added as child of this one. */ virtual void setCanvas(KoCanvasBase *canvas) = 0; /** * Return the currently set canvas. The default implementation will return Null * @return the currently set canvas */ virtual KoCanvasBase *canvas() const; /** * return the amount of pixels vertically visible of the child canvas. * @return the amount of pixels vertically visible of the child canvas. */ virtual int visibleHeight() const = 0; /** * return the amount of pixels horizontally visible of the child canvas. * @return the amount of pixels horizontally visible of the child canvas. */ virtual int visibleWidth() const = 0; /** * return the amount of pixels that are not visible on the left side of the canvas. * The leftmost pixel that is shown is returned. */ virtual int canvasOffsetX() const = 0; /** * return the amount of pixels that are not visible on the top side of the canvas. * The topmost pixel that is shown is returned. */ virtual int canvasOffsetY() const = 0; /** * @brief Scrolls the content of the canvas so that the given rect is visible. * * The rect is to be specified in view coordinates (pixels). The scrollbar positions * are changed so that the centerpoint of the rectangle is centered if possible. * * @param rect the rectangle to make visible * @param smooth if true the viewport translation will make be just enough to ensure visibility, no more. * @see KoViewConverter::documentToView() */ virtual void ensureVisible(const QRectF &rect, bool smooth = false) = 0; /** * @brief Scrolls the content of the canvas so that the given shape is visible. * * This is just a wrapper function of the above function. * * @param shape the shape to make visible */ virtual void ensureVisible(KoShape *shape) = 0; /** * @brief zooms in around the center. * * The center must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center becomes center if possible. * * @param center the position to zoom in on */ virtual void zoomIn(const QPoint ¢er) = 0; /** * @brief zooms out around the center. * * The center must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center becomes center if possible. * * @param center the position to zoom out around */ virtual void zoomOut(const QPoint ¢er) = 0; /** * @brief zooms around the center. * * The center must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center becomes center if possible. * * @param center the position to zoom around * @param zoom the zoom to apply */ virtual void zoomBy(const QPoint ¢er, qreal zoom) = 0; /** * @brief zoom so that rect is exactly visible (as close as possible) * * The rect must be specified in view coordinates (pixels). The scrollbar positions * are changed so that the center of the rect becomes center if possible. * * @param rect the rect in view coordinates (pixels) that should fit the view afterwards */ virtual void zoomTo(const QRect &rect) = 0; /** * @brief repositions the scrollbars so previous center is once again center * * The previous center is cached from when the user uses the scrollbars or zoomTo * are called. zoomTo is mostly used when a zoom tool of sorts have marked an area * to zoom in on * * The success of this method is limited by the size of thing. But we try our best. */ virtual void recenterPreferred() = 0; /** * Sets the preferred center point in view coordinates (pixels). * @param viewPoint the new preferred center */ virtual void setPreferredCenter(const QPointF &viewPoint) = 0; /// Returns the currently set preferred center point in view coordinates (pixels) virtual QPointF preferredCenter() const = 0; /** * Move the canvas over the x and y distance of the parameter distance * @param distance the distance in view coordinates (pixels). A positive distance means moving the canvas up/left. */ virtual void pan(const QPoint &distance) = 0; /** * Get the position of the scrollbar */ virtual QPoint scrollBarValue() const = 0; /** * Set the position of the scrollbar * @param value the new values of the scroll bars */ virtual void setScrollBarValue(const QPoint &value) = 0; /** * Called when the size of your document in view coordinates (pixels) changes, for instance when zooming. * * @param newSize the new size, in view coordinates (pixels), of the document. * @param recalculateCenter if true the offset in the document we center on after calling * recenterPreferred() will be recalculated for the new document size so the visual offset stays the same. */ virtual void updateDocumentSize(const QSize &sz, bool recalculateCenter) = 0; /** * Set mouse wheel to zoom behaviour * @param zoom if true wheel will zoom instead of scroll, control modifier will scroll */ virtual void setZoomWithWheel(bool zoom) = 0; /** * Set scroll area to be bigger than actual document. * It allows the user to move the corner of the document * to e.g. the center of the screen * * @param factor the coefficient, defining how much we can scroll out, * measured in parts of the widget size. Null value means vast * scrolling is disabled. */ virtual void setVastScrolling(qreal factor) = 0; /** * Returns the action collection for the canvas * @returns action collection for this canvas, can be 0 */ virtual KActionCollection* actionCollection() const; QPoint documentOffset() const; + /** + * @return the current position of the cursor fetched from QCursor::pos() and + * converted into document coordinates + */ + virtual QPointF currentCursorPosition() const = 0; + protected: void setDocumentSize(const QSize &sz); QSize documentSize() const; void setPreferredCenterFractionX(qreal); qreal preferredCenterFractionX() const; void setPreferredCenterFractionY(qreal); qreal preferredCenterFractionY() const; void setDocumentOffset( QPoint &offset); private: class Private; Private * const d; }; /** * Workaround class for the problem that Qt does not allow two QObject base classes. * KoCanvasController can be implemented by for instance QWidgets, so it cannot be * a QObject directly. The interface of this class should be considered public interface * for KoCanvasController. */ class KRITAFLAKE_EXPORT KoCanvasControllerProxyObject : public QObject { Q_OBJECT Q_DISABLE_COPY(KoCanvasControllerProxyObject) public: explicit KoCanvasControllerProxyObject(KoCanvasController *canvasController, QObject *parent = 0); public: // Convenience methods to invoke the signals from subclasses void emitCanvasRemoved(KoCanvasController *canvasController) { emit canvasRemoved(canvasController); } void emitCanvasSet(KoCanvasController *canvasController) { emit canvasSet(canvasController); } void emitCanvasOffsetXChanged(int offset) { emit canvasOffsetXChanged(offset); } void emitCanvasOffsetYChanged(int offset) { emit canvasOffsetYChanged(offset); } void emitCanvasMousePositionChanged(const QPoint &position) { emit canvasMousePositionChanged(position); } void emitDocumentMousePositionChanged(const QPointF &position) { emit documentMousePositionChanged(position); } void emitSizeChanged(const QSize &size) { emit sizeChanged(size); } void emitMoveDocumentOffset(const QPoint &point) { emit moveDocumentOffset(point); } void emitZoomRelative(const qreal factor, const QPointF &stillPoint) { emit zoomRelative(factor, stillPoint); } // Convenience method to retrieve the canvas controller for who needs to use QPointer KoCanvasController *canvasController() const { return m_canvasController; } Q_SIGNALS: /** * Emitted when a previously added canvas is about to be removed. * @param canvasController this object */ void canvasRemoved(KoCanvasController *canvasController); /** * Emitted when a canvas is set on this widget * @param canvasController this object */ void canvasSet(KoCanvasController *canvasController); /** * Emitted when canvasOffsetX() changes * @param offset the new canvas offset */ void canvasOffsetXChanged(int offset); /** * Emitted when canvasOffsetY() changes * @param offset the new canvas offset */ void canvasOffsetYChanged(int offset); /** * Emitted when the cursor is moved over the canvas widget. * @param position the position in view coordinates (pixels). */ void canvasMousePositionChanged(const QPoint &position); /** * Emitted when the cursor is moved over the canvas widget. * @param position the position in document coordinates. * * Use \ref canvasMousePositionChanged to get the position * in view coordinates. */ void documentMousePositionChanged(const QPointF &position); /** * Emitted when the entire controller size changes * @param size the size in widget pixels. */ void sizeChanged(const QSize &size); /** * Emitted whenever the document is scrolled. * * @param point the new top-left point from which the document should * be drawn. */ void moveDocumentOffset(const QPoint &point); /** * Emitted when zoomRelativeToPoint have calculated a factor by which * the zoom should change and the point which should stand still * on screen. * Someone needs to connect to this and take action * * @param factor by how much the zoom needs to change. * @param stillPoint the point which will not change its position * in widget during the zooming. It is measured in * view coordinate system *before* zoom. */ void zoomRelative(const qreal factor, const QPointF &stillPoint); public Q_SLOTS: /** * Call this slot whenever the size of your document in view coordinates (pixels) * changes, for instance when zooming. * @param newSize the new size, in view coordinates (pixels), of the document. * @param recalculateCenter if true the offset in the document we center on after calling * recenterPreferred() will be recalculated for the new document size so the visual offset stays the same. */ void updateDocumentSize(const QSize &newSize, bool recalculateCenter = true); private: KoCanvasController *m_canvasController; }; class KRITAFLAKE_EXPORT KoDummyCanvasController : public KoCanvasController { public: explicit KoDummyCanvasController(KActionCollection* actionCollection) : KoCanvasController(actionCollection) {} virtual ~KoDummyCanvasController() {} virtual void scrollContentsBy(int /*dx*/, int /*dy*/) {} virtual QSize viewportSize() const { return QSize(); } virtual void setDrawShadow(bool /*drawShadow*/) {} virtual void setCanvas(KoCanvasBase *canvas) {Q_UNUSED(canvas)} virtual KoCanvasBase *canvas() const {return 0;} virtual int visibleHeight() const {return 0;} virtual int visibleWidth() const {return 0;} virtual int canvasOffsetX() const {return 0;} virtual int canvasOffsetY() const {return 0;} virtual void ensureVisible(const QRectF &/*rect*/, bool /*smooth */ = false) {} virtual void ensureVisible(KoShape *shape) {Q_UNUSED(shape)} virtual void zoomIn(const QPoint &/*center*/) {} virtual void zoomOut(const QPoint &/*center*/) {} virtual void zoomBy(const QPoint &/*center*/, qreal /*zoom*/) {} virtual void zoomTo(const QRect &/*rect*/) {} virtual void recenterPreferred() {} virtual void setPreferredCenter(const QPointF &/*viewPoint*/) {} virtual QPointF preferredCenter() const {return QPointF();} virtual void pan(const QPoint &/*distance*/) {} virtual QPoint scrollBarValue() const {return QPoint();} virtual void setScrollBarValue(const QPoint &/*value*/) {} virtual void updateDocumentSize(const QSize &/*sz*/, bool /*recalculateCenter*/) {} virtual void setZoomWithWheel(bool /*zoom*/) {} virtual void setVastScrolling(qreal /*factor*/) {} + QPointF currentCursorPosition() const override { return QPointF(); } }; #endif diff --git a/libs/flake/KoCanvasControllerWidget.cpp b/libs/flake/KoCanvasControllerWidget.cpp index 91695c757c..820559bd72 100644 --- a/libs/flake/KoCanvasControllerWidget.cpp +++ b/libs/flake/KoCanvasControllerWidget.cpp @@ -1,588 +1,595 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2008-2009 Thomas Zander * Copyright (C) 2006 Peter Simonsson * Copyright (C) 2006, 2009 Thorsten Zachmann * Copyright (C) 2007-2010 Boudewijn Rempt * Copyright (C) 2007 C. Boemann * Copyright (C) 2006-2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCanvasControllerWidget.h" #include "KoCanvasControllerWidget_p.h" #include "KoCanvasControllerWidgetViewport_p.h" #include "KoShape.h" #include "KoViewConverter.h" #include "KoCanvasBase.h" #include "KoCanvasObserverBase.h" #include "KoCanvasSupervisor.h" #include "KoToolManager_p.h" #include #include #include #include #include #include #include #include #include #include void KoCanvasControllerWidget::Private::setDocumentOffset() { // The margins scroll the canvas widget inside the viewport, not // the document. The documentOffset is meant to be the value that // the canvas must add to the update rect in its paint event, to // compensate. QPoint pt(q->horizontalScrollBar()->value(), q->verticalScrollBar()->value()); q->proxyObject->emitMoveDocumentOffset(pt); QWidget *canvasWidget = canvas->canvasWidget(); if (canvasWidget) { // If it isn't an OpenGL canvas if (qobject_cast(canvasWidget) == 0) { QPoint diff = q->documentOffset() - pt; if (q->canvasMode() == Spreadsheet && canvasWidget->layoutDirection() == Qt::RightToLeft) { canvasWidget->scroll(-diff.x(), diff.y()); } else { canvasWidget->scroll(diff.x(), diff.y()); } } } q->setDocumentOffset(pt); } void KoCanvasControllerWidget::Private::resetScrollBars() { // The scrollbar value always points at the top-left corner of the // bit of image we paint. int docH = q->documentSize().height() + q->margin(); int docW = q->documentSize().width() + q->margin(); int drawH = viewportWidget->height(); int drawW = viewportWidget->width(); QScrollBar *hScroll = q->horizontalScrollBar(); QScrollBar *vScroll = q->verticalScrollBar(); int horizontalReserve = vastScrollingFactor * drawW; int verticalReserve = vastScrollingFactor * drawH; int xMin = -horizontalReserve; int yMin = -verticalReserve; int xMax = docW - drawW + horizontalReserve; int yMax = docH - drawH + verticalReserve; hScroll->setRange(xMin, xMax); vScroll->setRange(yMin, yMax); int fontheight = QFontMetrics(q->font()).height(); vScroll->setPageStep(drawH); vScroll->setSingleStep(fontheight); hScroll->setPageStep(drawW); hScroll->setSingleStep(fontheight); } void KoCanvasControllerWidget::Private::emitPointerPositionChangedSignals(QEvent *event) { if (!canvas) return; if (!canvas->viewConverter()) return; QPoint pointerPos; QMouseEvent *mouseEvent = dynamic_cast(event); if (mouseEvent) { pointerPos = mouseEvent->pos(); } else { QTabletEvent *tabletEvent = dynamic_cast(event); if (tabletEvent) { pointerPos = tabletEvent->pos(); } } QPoint pixelPos = (pointerPos - canvas->documentOrigin()) + q->documentOffset(); QPointF documentPos = canvas->viewConverter()->viewToDocument(pixelPos); q->proxyObject->emitDocumentMousePositionChanged(documentPos); q->proxyObject->emitCanvasMousePositionChanged(pointerPos); } #include void KoCanvasControllerWidget::Private::activate() { QWidget *parent = q; while (parent->parentWidget()) { parent = parent->parentWidget(); } KoCanvasSupervisor *observerProvider = dynamic_cast(parent); if (!observerProvider) { return; } Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observer->setObservedCanvas(q->canvas()); } } } void KoCanvasControllerWidget::Private::unsetCanvas() { QWidget *parent = q; while (parent->parentWidget()) { parent = parent->parentWidget(); } KoCanvasSupervisor *observerProvider = dynamic_cast(parent); if (!observerProvider) { return; } Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer && observer->observedCanvas() == q->canvas()) { observer->unsetObservedCanvas(); } } } //////////// KoCanvasControllerWidget::KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent) : QAbstractScrollArea(parent) , KoCanvasController(actionCollection) , d(new Private(this)) { // We need to set this as QDeclarativeView sets them a bit differnt from QAbstractScrollArea setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // And then our own Viewport d->viewportWidget = new Viewport(this); setViewport(d->viewportWidget); d->viewportWidget->setFocusPolicy(Qt::NoFocus); setFocusPolicy(Qt::NoFocus); setFrameStyle(0); //setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setAutoFillBackground(false); /* Fixes: apps starting at zero zoom. Details: Since the document is set on the mainwindow before loading commences the inial show/layout can choose to set the document to be very small, even to be zero pixels tall. Setting a sane minimum size on the widget means we no loger get rounding errors in zooming and we no longer end up with zero-zoom. Note: KoPage apps should probably startup with a sane document size; for Krita that's impossible */ setMinimumSize(QSize(50, 50)); setMouseTracking(true); connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetX())); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetY())); connect(d->viewportWidget, SIGNAL(sizeChanged()), this, SLOT(updateCanvasOffsetX())); connect(proxyObject, SIGNAL(moveDocumentOffset(const QPoint&)), d->viewportWidget, SLOT(documentOffsetMoved(const QPoint&))); } KoCanvasControllerWidget::~KoCanvasControllerWidget() { d->unsetCanvas(); delete d; } void KoCanvasControllerWidget::activate() { d->activate(); } void KoCanvasControllerWidget::scrollContentsBy(int dx, int dy) { Q_UNUSED(dx); Q_UNUSED(dy); d->setDocumentOffset(); } QSize KoCanvasControllerWidget::viewportSize() const { return viewport()->size(); } void KoCanvasControllerWidget::setDrawShadow(bool drawShadow) { d->viewportWidget->setDrawShadow(drawShadow); } void KoCanvasControllerWidget::resizeEvent(QResizeEvent *resizeEvent) { proxyObject->emitSizeChanged(resizeEvent->size()); // XXX: When resizing, keep the area we're looking at now in the // center of the resized view. d->resetScrollBars(); d->setDocumentOffset(); } void KoCanvasControllerWidget::setCanvas(KoCanvasBase *canvas) { Q_ASSERT(canvas); // param is not null if (d->canvas) { d->unsetCanvas(); proxyObject->emitCanvasRemoved(this); canvas->setCanvasController(0); d->canvas->canvasWidget()->removeEventFilter(this); } canvas->setCanvasController(this); d->canvas = canvas; changeCanvasWidget(canvas->canvasWidget()); proxyObject->emitCanvasSet(this); QTimer::singleShot(0, this, SLOT(activate())); setPreferredCenterFractionX(0); setPreferredCenterFractionY(0); } KoCanvasBase* KoCanvasControllerWidget::canvas() const { return d->canvas; } void KoCanvasControllerWidget::changeCanvasWidget(QWidget *widget) { if (d->viewportWidget->canvas()) { widget->setCursor(d->viewportWidget->canvas()->cursor()); d->viewportWidget->canvas()->removeEventFilter(this); } d->viewportWidget->setCanvas(widget); setFocusProxy(d->canvas->canvasWidget()); } int KoCanvasControllerWidget::visibleHeight() const { if (d->canvas == 0) return 0; QWidget *canvasWidget = canvas()->canvasWidget(); int height1; if (canvasWidget == 0) height1 = viewport()->height(); else height1 = qMin(viewport()->height(), canvasWidget->height()); int height2 = height(); return qMin(height1, height2); } int KoCanvasControllerWidget::visibleWidth() const { if (d->canvas == 0) return 0; QWidget *canvasWidget = canvas()->canvasWidget(); int width1; if (canvasWidget == 0) width1 = viewport()->width(); else width1 = qMin(viewport()->width(), canvasWidget->width()); int width2 = width(); return qMin(width1, width2); } int KoCanvasControllerWidget::canvasOffsetX() const { int offset = -horizontalScrollBar()->value(); if (d->canvas) { offset += d->canvas->canvasWidget()->x() + frameWidth(); } return offset; } int KoCanvasControllerWidget::canvasOffsetY() const { int offset = -verticalScrollBar()->value(); if (d->canvas) { offset += d->canvas->canvasWidget()->y() + frameWidth(); } return offset; } void KoCanvasControllerWidget::updateCanvasOffsetX() { proxyObject->emitCanvasOffsetXChanged(canvasOffsetX()); if (d->ignoreScrollSignals) return; setPreferredCenterFractionX((horizontalScrollBar()->value() + viewport()->width() / 2.0) / documentSize().width()); } void KoCanvasControllerWidget::updateCanvasOffsetY() { proxyObject->emitCanvasOffsetYChanged(canvasOffsetY()); if (d->ignoreScrollSignals) return; setPreferredCenterFractionY((verticalScrollBar()->value() + verticalScrollBar()->pageStep() / 2.0) / documentSize().height()); } void KoCanvasControllerWidget::ensureVisible(KoShape *shape) { Q_ASSERT(shape); ensureVisible(d->canvas->viewConverter()->documentToView(shape->boundingRect())); } void KoCanvasControllerWidget::ensureVisible(const QRectF &rect, bool smooth) { QRect currentVisible(-canvasOffsetX(), -canvasOffsetY(), visibleWidth(), visibleHeight()); QRect viewRect = rect.toRect(); viewRect.translate(d->canvas->documentOrigin()); if (!viewRect.isValid() || currentVisible.contains(viewRect)) return; // its visible. Nothing to do. // if we move, we move a little more so the amount of times we have to move is less. int jumpWidth = smooth ? 0 : currentVisible.width() / 5; int jumpHeight = smooth ? 0 : currentVisible.height() / 5; if (!smooth && viewRect.width() + jumpWidth > currentVisible.width()) jumpWidth = 0; if (!smooth && viewRect.height() + jumpHeight > currentVisible.height()) jumpHeight = 0; int horizontalMove = 0; if (currentVisible.width() <= viewRect.width()) // center view horizontalMove = viewRect.center().x() - currentVisible.center().x(); else if (currentVisible.x() > viewRect.x()) // move left horizontalMove = viewRect.x() - currentVisible.x() - jumpWidth; else if (currentVisible.right() < viewRect.right()) // move right horizontalMove = viewRect.right() - qMax(0, currentVisible.right() - jumpWidth); int verticalMove = 0; if (currentVisible.height() <= viewRect.height()) // center view verticalMove = viewRect.center().y() - currentVisible.center().y(); if (currentVisible.y() > viewRect.y()) // move up verticalMove = viewRect.y() - currentVisible.y() - jumpHeight; else if (currentVisible.bottom() < viewRect.bottom()) // move down verticalMove = viewRect.bottom() - qMax(0, currentVisible.bottom() - jumpHeight); pan(QPoint(horizontalMove, verticalMove)); } void KoCanvasControllerWidget::recenterPreferred() { const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; QPointF center = preferredCenter(); // convert into a viewport based point center.rx() += d->canvas->canvasWidget()->x() + frameWidth(); center.ry() += d->canvas->canvasWidget()->y() + frameWidth(); // scroll to a new center point QPointF topLeft = center - 0.5 * QPointF(viewport()->width(), viewport()->height()); setScrollBarValue(topLeft.toPoint()); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::zoomIn(const QPoint ¢er) { zoomBy(center, sqrt(2.0)); } void KoCanvasControllerWidget::zoomOut(const QPoint ¢er) { zoomBy(center, sqrt(0.5)); } void KoCanvasControllerWidget::zoomBy(const QPoint ¢er, qreal zoom) { setPreferredCenterFractionX(1.0 * center.x() / documentSize().width()); setPreferredCenterFractionY(1.0 * center.y() / documentSize().height()); const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; proxyObject->emitZoomRelative(zoom, preferredCenter()); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::zoomTo(const QRect &viewRect) { qreal scale; if (1.0 * viewport()->width() / viewRect.width() > 1.0 * viewport()->height() / viewRect.height()) scale = 1.0 * viewport()->height() / viewRect.height(); else scale = 1.0 * viewport()->width() / viewRect.width(); zoomBy(viewRect.center(), scale); } void KoCanvasControllerWidget::updateDocumentSize(const QSize &sz, bool recalculateCenter) { // Don't update if the document-size didn't changed to prevent infinite loops and unneeded updates. if (KoCanvasController::documentSize() == sz) return; if (!recalculateCenter) { // assume the distance from the top stays equal and recalculate the center. setPreferredCenterFractionX(documentSize().width() * preferredCenterFractionX() / sz.width()); setPreferredCenterFractionY(documentSize().height() * preferredCenterFractionY() / sz.height()); } const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; KoCanvasController::setDocumentSize(sz); d->viewportWidget->setDocumentSize(sz); d->resetScrollBars(); // Always emit the new offset. updateCanvasOffsetX(); updateCanvasOffsetY(); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::setZoomWithWheel(bool zoom) { d->zoomWithWheel = zoom; } void KoCanvasControllerWidget::setVastScrolling(qreal factor) { d->vastScrollingFactor = factor; } +QPointF KoCanvasControllerWidget::currentCursorPosition() const +{ + QWidget *canvasWidget = d->canvas->canvasWidget(); + const KoViewConverter *converter = d->canvas->viewConverter(); + return converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + d->canvas->canvasController()->documentOffset() - canvasWidget->pos()); +} + void KoCanvasControllerWidget::pan(const QPoint &distance) { QPoint sourcePoint = scrollBarValue(); setScrollBarValue(sourcePoint + distance); } void KoCanvasControllerWidget::setPreferredCenter(const QPointF &viewPoint) { setPreferredCenterFractionX(viewPoint.x() / documentSize().width()); setPreferredCenterFractionY(viewPoint.y() / documentSize().height()); recenterPreferred(); } QPointF KoCanvasControllerWidget::preferredCenter() const { QPointF center; center.setX(preferredCenterFractionX() * documentSize().width()); center.setY(preferredCenterFractionY() * documentSize().height()); return center; } void KoCanvasControllerWidget::paintEvent(QPaintEvent *event) { QPainter gc(viewport()); d->viewportWidget->handlePaintEvent(gc, event); } void KoCanvasControllerWidget::dragEnterEvent(QDragEnterEvent *event) { d->viewportWidget->handleDragEnterEvent(event); } void KoCanvasControllerWidget::dropEvent(QDropEvent *event) { d->viewportWidget->handleDropEvent(event); } void KoCanvasControllerWidget::dragMoveEvent(QDragMoveEvent *event) { d->viewportWidget->handleDragMoveEvent(event); } void KoCanvasControllerWidget::dragLeaveEvent(QDragLeaveEvent *event) { d->viewportWidget->handleDragLeaveEvent(event); } void KoCanvasControllerWidget::wheelEvent(QWheelEvent *event) { if (d->zoomWithWheel != ((event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier)) { const qreal zoomCoeff = event->delta() > 0 ? sqrt(2.0) : sqrt(0.5); zoomRelativeToPoint(event->pos(), zoomCoeff); event->accept(); } else QAbstractScrollArea::wheelEvent(event); } void KoCanvasControllerWidget::zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff) { const QPoint offset = scrollBarValue(); const QPoint mousePos(widgetPoint + offset); const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; proxyObject->emitZoomRelative(zoomCoeff, mousePos); d->ignoreScrollSignals = oldIgnoreScrollSignals; } bool KoCanvasControllerWidget::focusNextPrevChild(bool) { // we always return false meaning the canvas takes keyboard focus, but never gives it away. return false; } void KoCanvasControllerWidget::setMargin(int margin) { KoCanvasController::setMargin(margin); Q_ASSERT(d->viewportWidget); d->viewportWidget->setMargin(margin); } QPoint KoCanvasControllerWidget::scrollBarValue() const { QScrollBar * hBar = horizontalScrollBar(); QScrollBar * vBar = verticalScrollBar(); return QPoint(hBar->value(), vBar->value()); } void KoCanvasControllerWidget::setScrollBarValue(const QPoint &value) { QScrollBar * hBar = horizontalScrollBar(); QScrollBar * vBar = verticalScrollBar(); hBar->setValue(value.x()); vBar->setValue(value.y()); } KoCanvasControllerWidget::Private *KoCanvasControllerWidget::priv() { return d; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoCanvasControllerWidget.cpp" diff --git a/libs/flake/KoCanvasControllerWidget.h b/libs/flake/KoCanvasControllerWidget.h index f8f3bf1da7..68d9d80f2f 100644 --- a/libs/flake/KoCanvasControllerWidget.h +++ b/libs/flake/KoCanvasControllerWidget.h @@ -1,182 +1,184 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2008 Thomas Zander * Copyright (C) 2007-2010 Boudewijn Rempt * Copyright (C) 2007-2008 C. Boemann * Copyright (C) 2006-2007 Jan Hambrecht * Copyright (C) 2009 Thorsten Zachmann * * 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 KOCANVASCONTROLLERWIDGET_H #define KOCANVASCONTROLLERWIDGET_H #include "kritaflake_export.h" #include #include #include "KoCanvasController.h" class KoShape; class KoCanvasBase; /** * KoCanvasController implementation for QWidget based canvases */ class KRITAFLAKE_EXPORT KoCanvasControllerWidget : public QAbstractScrollArea, public KoCanvasController { Q_OBJECT public: /** * Constructor. * @param parent the parent this widget will belong to */ explicit KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent = 0); virtual ~KoCanvasControllerWidget(); /** * Reimplemented from QAbstractScrollArea. */ void scrollContentsBy(int dx, int dy); virtual QSize viewportSize() const; /// Reimplemented from KoCanvasController /** * Activate this canvascontroller */ virtual void activate(); virtual void setDrawShadow(bool drawShadow); virtual void setCanvas(KoCanvasBase *canvas); virtual KoCanvasBase *canvas() const; /** * Change the actual canvas widget used by the current canvas. This allows the canvas widget * to be changed while keeping the current KoCanvasBase canvas and its associated resources as * they are. This might be used, for example, to switch from a QWidget to a QOpenGLWidget canvas. * @param widget the new canvas widget. */ virtual void changeCanvasWidget(QWidget *widget); virtual int visibleHeight() const; virtual int visibleWidth() const; virtual int canvasOffsetX() const; virtual int canvasOffsetY() const; virtual void ensureVisible(const QRectF &rect, bool smooth = false); virtual void ensureVisible(KoShape *shape); /** * will cause the toolOptionWidgetsChanged to be emitted and all * listeners to be updated to the new widget. * * FIXME: This doesn't belong her and it does an * inherits("KoView") so it too much tied to komain * * @param widgets the map of widgets */ void setToolOptionWidgets(const QList > &widgets); virtual void zoomIn(const QPoint ¢er); virtual void zoomOut(const QPoint ¢er); virtual void zoomBy(const QPoint ¢er, qreal zoom); virtual void zoomTo(const QRect &rect); /** * Zoom document keeping point \p widgetPoint unchanged * \param widgetPoint sticky point in widget pixels */ virtual void zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff); virtual void recenterPreferred(); virtual void setPreferredCenter(const QPointF &viewPoint); /// Returns the currently set preferred center point in view coordinates (pixels) virtual QPointF preferredCenter() const; virtual void pan(const QPoint &distance); virtual void setMargin(int margin); virtual QPoint scrollBarValue() const; /** * Used by KisCanvasController to correct the scrollbars position * after the rotation. */ virtual void setScrollBarValue(const QPoint &value); virtual void updateDocumentSize(const QSize &sz, bool recalculateCenter = true); /** * Set mouse wheel to zoom behaviour * @param zoom if true wheel will zoom instead of scroll, control modifier will scroll */ void setZoomWithWheel(bool zoom); virtual void setVastScrolling(qreal factor); + QPointF currentCursorPosition() const override; + /** * \internal */ class Private; KoCanvasControllerWidget::Private *priv(); private Q_SLOTS: /// Called by the horizontal scrollbar when its value changes void updateCanvasOffsetX(); /// Called by the vertical scrollbar when its value changes void updateCanvasOffsetY(); protected: friend class KisZoomAndPanTest; /// reimplemented from QWidget virtual void paintEvent(QPaintEvent *event); /// reimplemented from QWidget virtual void resizeEvent(QResizeEvent *resizeEvent); /// reimplemented from QWidget virtual void dragEnterEvent(QDragEnterEvent *event); /// reimplemented from QWidget virtual void dropEvent(QDropEvent *event); /// reimplemented from QWidget virtual void dragMoveEvent(QDragMoveEvent *event); /// reimplemented from QWidget virtual void dragLeaveEvent(QDragLeaveEvent *event); /// reimplemented from QWidget virtual void wheelEvent(QWheelEvent *event); /// reimplemented from QWidget virtual bool focusNextPrevChild(bool next); private: Q_PRIVATE_SLOT(d, void activate()) Private * const d; }; #endif diff --git a/libs/flake/KoDrag.cpp b/libs/flake/KoDrag.cpp index 2f82798c27..51b9f1bd07 100644 --- a/libs/flake/KoDrag.cpp +++ b/libs/flake/KoDrag.cpp @@ -1,149 +1,117 @@ /* This file is part of the KDE project * Copyright (C) 2007-2008 Thorsten Zachmann * Copyright (C) 2009 Thomas Zander * * 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 "KoDrag.h" #include "KoDragOdfSaveHelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoShapeSavingContext.h" +#include +#include +#include + + class KoDragPrivate { public: KoDragPrivate() : mimeData(0) { } ~KoDragPrivate() { delete mimeData; } QMimeData *mimeData; }; KoDrag::KoDrag() : d(new KoDragPrivate()) { } KoDrag::~KoDrag() { delete d; } -bool KoDrag::setOdf(const char *mimeType, KoDragOdfSaveHelper &helper) +bool KoDrag::setOdf(const char *, KoDragOdfSaveHelper &) { - struct Finally { - Finally(KoStore *s) : store(s) { } - ~Finally() { - delete store; - } - KoStore *store; - }; - - QBuffer buffer; - KoStore *store = KoStore::createStore(&buffer, KoStore::Write, mimeType); - Finally finally(store); // delete store when we exit this scope - Q_ASSERT(store); - Q_ASSERT(!store->bad()); - - KoOdfWriteStore odfStore(store); - KoEmbeddedDocumentSaver embeddedSaver; - - KoXmlWriter *manifestWriter = odfStore.manifestWriter(mimeType); - KoXmlWriter *contentWriter = odfStore.contentWriter(); - - if (!contentWriter) { - return false; - } + return false; +} - KoGenStyles mainStyles; - KoXmlWriter *bodyWriter = odfStore.bodyWriter(); - KoShapeSavingContext *context = helper.context(bodyWriter, mainStyles, embeddedSaver); +bool KoDrag::setSvg(const QList originalShapes) +{ + QRectF boundingRect; + QList shapes; - if (!helper.writeBody()) { - return false; + Q_FOREACH (KoShape *shape, originalShapes) { + boundingRect |= shape->boundingRect(); + shapes.append(shape->cloneShape()); } - mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, contentWriter); + qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); - odfStore.closeContentWriter(); - - //add manifest line for content.xml - manifestWriter->addManifestEntry("content.xml", "text/xml"); - - - if (!mainStyles.saveOdfStylesDotXml(store, manifestWriter)) { - return false; - } - - if (!context->saveDataCenter(store, manifestWriter)) { - debugFlake << "save data centers failed"; - return false; - } + QBuffer buffer; + QLatin1String mimeType("image/svg+xml"); - // Save embedded objects - KoDocumentBase::SavingContext documentContext(odfStore, embeddedSaver); - if (!embeddedSaver.saveEmbeddedDocuments(documentContext)) { - debugFlake << "save embedded documents failed"; - return false; - } + buffer.open(QIODevice::WriteOnly); - // Write out manifest file - if (!odfStore.closeManifestWriter()) { - return false; - } + const QSizeF pageSize(boundingRect.right(), boundingRect.bottom()); + SvgWriter writer(shapes, pageSize); + writer.save(buffer); - delete store; // make sure the buffer if fully flushed. - finally.store = 0; - setData(mimeType, buffer.buffer()); + buffer.close(); + qDeleteAll(shapes); + setData(mimeType, buffer.data()); return true; } void KoDrag::setData(const QString &mimeType, const QByteArray &data) { if (d->mimeData == 0) { d->mimeData = new QMimeData(); } d->mimeData->setData(mimeType, data); } void KoDrag::addToClipboard() { if (d->mimeData) { QApplication::clipboard()->setMimeData(d->mimeData); d->mimeData = 0; } } QMimeData * KoDrag::mimeData() { QMimeData *mimeData = d->mimeData; d->mimeData = 0; return mimeData; } diff --git a/libs/flake/KoDrag.h b/libs/flake/KoDrag.h index 8ef1c26b0f..db1938fe0f 100644 --- a/libs/flake/KoDrag.h +++ b/libs/flake/KoDrag.h @@ -1,78 +1,83 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thorsten Zachmann * * 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 KODRAG_H #define KODRAG_H #include "kritaflake_export.h" +#include + class QMimeData; class QString; class QByteArray; class KoDragOdfSaveHelper; class KoDragPrivate; +class KoShape; /** * Class for simplifying adding a odf to the clip board * * For saving the odf a KoDragOdfSaveHelper class is used. * It implements the writing of the body of the document. The * setOdf takes care of saving styles and all the other * common stuff. */ class KRITAFLAKE_EXPORT KoDrag { public: KoDrag(); ~KoDrag(); /** * Set odf mime type * * This calls helper.writeBody(); * * @param mimeType used for creating the odf document * @param helper helper for saving the body of the odf document */ - bool setOdf(const char *mimeType, KoDragOdfSaveHelper &helper); + bool setOdf(const char *, KoDragOdfSaveHelper &); + + bool setSvg(const QList shapes); /** * Add additional mimeTypes */ void setData(const QString &mimeType, const QByteArray &data); /** * Add the mimeData to the clipboard */ void addToClipboard(); /** * Get the mime data * * This transfers the ownership of the mimeData to the caller * * This function is for use in automated tests */ QMimeData *mimeData(); private: KoDragPrivate * const d; }; #endif /* KODRAG_H */ diff --git a/libs/flake/KoShape.cpp b/libs/flake/KoShape.cpp index d1bb8c4226..bdaacdeec6 100644 --- a/libs/flake/KoShape.cpp +++ b/libs/flake/KoShape.cpp @@ -1,2398 +1,2407 @@ /* This file is part of the KDE project Copyright (C) 2006 C. Boemann Rasmussen Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2006-2010 Thorsten Zachmann Copyright (C) 2007-2009,2011 Jan Hambrecht CopyRight (C) 2010 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 "KoShape.h" #include "KoShape_p.h" #include "KoShapeContainer.h" #include "KoShapeLayer.h" #include "KoShapeContainerModel.h" #include "KoSelection.h" #include "KoPointerEvent.h" #include "KoInsets.h" #include "KoShapeStrokeModel.h" #include "KoShapeBackground.h" #include "KoColorBackground.h" #include "KoHatchBackground.h" #include "KoGradientBackground.h" #include "KoPatternBackground.h" #include "KoShapeManager.h" #include "KoShapeUserData.h" #include "KoShapeApplicationData.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoViewConverter.h" #include "KoShapeStroke.h" #include "KoShapeShadow.h" #include "KoClipPath.h" #include "KoPathShape.h" #include "KoOdfWorkaround.h" #include "KoFilterEffectStack.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_assert.h" #include #include "KoOdfGradientBackground.h" #include // KoShapePrivate KoShapePrivate::KoShapePrivate(KoShape *shape) : q_ptr(shape), size(50, 50), parent(0), shadow(0), border(0), filterEffectStack(0), transparency(0.0), zIndex(0), runThrough(0), visible(true), printable(true), geometryProtected(false), keepAspect(false), selectable(true), detectCollision(false), protectContent(false), textRunAroundSide(KoShape::BiggestRunAroundSide), textRunAroundDistanceLeft(0.0), textRunAroundDistanceTop(0.0), textRunAroundDistanceRight(0.0), textRunAroundDistanceBottom(0.0), textRunAroundThreshold(0.0), textRunAroundContour(KoShape::ContourFull) { connectors[KoConnectionPoint::TopConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::TopConnectionPoint); connectors[KoConnectionPoint::RightConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::RightConnectionPoint); connectors[KoConnectionPoint::BottomConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::BottomConnectionPoint); connectors[KoConnectionPoint::LeftConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::LeftConnectionPoint); connectors[KoConnectionPoint::FirstCustomConnectionPoint] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter); } KoShapePrivate::KoShapePrivate(const KoShapePrivate &rhs, KoShape *q) : q_ptr(q), size(rhs.size), shapeId(rhs.shapeId), name(rhs.name), localMatrix(rhs.localMatrix), connectors(rhs.connectors), parent(0), // to be initialized later shapeManagers(), // to be initialized later toolDelegates(), // FIXME: how to initialize them? userData(rhs.userData ? rhs.userData->clone() : 0), stroke(rhs.stroke), fill(rhs.fill), dependees(), // FIXME: how to initialize them? shadow(0), // WARNING: not implemented in Krita border(0), // WARNING: not implemented in Krita clipPath(rhs.clipPath ? rhs.clipPath->clone() : 0), clipMask(rhs.clipMask ? rhs.clipMask->clone() : 0), additionalAttributes(rhs.additionalAttributes), additionalStyleAttributes(rhs.additionalStyleAttributes), filterEffectStack(0), // WARNING: not implemented in Krita transparency(rhs.transparency), hyperLink(rhs.hyperLink), zIndex(rhs.zIndex), runThrough(rhs.runThrough), visible(rhs.visible), printable(rhs.visible), geometryProtected(rhs.geometryProtected), keepAspect(rhs.keepAspect), selectable(rhs.selectable), detectCollision(rhs.detectCollision), protectContent(rhs.protectContent), textRunAroundSide(rhs.textRunAroundSide), textRunAroundDistanceLeft(rhs.textRunAroundDistanceLeft), textRunAroundDistanceTop(rhs.textRunAroundDistanceTop), textRunAroundDistanceRight(rhs.textRunAroundDistanceRight), textRunAroundDistanceBottom(rhs.textRunAroundDistanceBottom), textRunAroundThreshold(rhs.textRunAroundThreshold), textRunAroundContour(rhs.textRunAroundContour) { } KoShapePrivate::~KoShapePrivate() { Q_Q(KoShape); if (parent) parent->removeShape(q); Q_FOREACH (KoShapeManager *manager, shapeManagers) { manager->remove(q); } if (shadow && !shadow->deref()) delete shadow; if (filterEffectStack && !filterEffectStack->deref()) delete filterEffectStack; } void KoShapePrivate::shapeChanged(KoShape::ChangeType type) { Q_Q(KoShape); if (parent) parent->model()->childChanged(q, type); q->shapeChanged(type); Q_FOREACH (KoShape * shape, dependees) { shape->shapeChanged(type, q); } Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners) { listener->notifyShapeChangedImpl(type, q); } } void KoShapePrivate::updateStroke() { Q_Q(KoShape); if (!stroke) return; KoInsets insets; stroke->strokeInsets(q, insets); QSizeF inner = q->size(); // update left q->update(QRectF(-insets.left, -insets.top, insets.left, inner.height() + insets.top + insets.bottom)); // update top q->update(QRectF(-insets.left, -insets.top, inner.width() + insets.left + insets.right, insets.top)); // update right q->update(QRectF(inner.width(), -insets.top, insets.right, inner.height() + insets.top + insets.bottom)); // update bottom q->update(QRectF(-insets.left, inner.height(), inner.width() + insets.left + insets.right, insets.bottom)); } void KoShapePrivate::addShapeManager(KoShapeManager *manager) { shapeManagers.insert(manager); } void KoShapePrivate::removeShapeManager(KoShapeManager *manager) { shapeManagers.remove(manager); } void KoShapePrivate::convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const { switch(point.alignment) { case KoConnectionPoint::AlignNone: point.position = KoFlake::toRelative(point.position, shapeSize); point.position.rx() = qBound(0.0, point.position.x(), 1.0); point.position.ry() = qBound(0.0, point.position.y(), 1.0); break; case KoConnectionPoint::AlignRight: point.position.rx() -= shapeSize.width(); case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() -= shapeSize.height(); case KoConnectionPoint::AlignTop: point.position.rx() = 0.5*shapeSize.width(); break; case KoConnectionPoint::AlignTopLeft: // nothing to do here break; case KoConnectionPoint::AlignTopRight: point.position.rx() -= shapeSize.width(); break; case KoConnectionPoint::AlignBottomLeft: point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignBottomRight: point.position.rx() -= shapeSize.width(); point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignCenter: point.position.rx() -= 0.5 * shapeSize.width(); point.position.ry() -= 0.5 * shapeSize.height(); break; } } void KoShapePrivate::convertToShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const { switch(point.alignment) { case KoConnectionPoint::AlignNone: point.position = KoFlake::toAbsolute(point.position, shapeSize); break; case KoConnectionPoint::AlignRight: point.position.rx() += shapeSize.width(); case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() += shapeSize.height(); case KoConnectionPoint::AlignTop: point.position.rx() = 0.5*shapeSize.width(); break; case KoConnectionPoint::AlignTopLeft: // nothing to do here break; case KoConnectionPoint::AlignTopRight: point.position.rx() += shapeSize.width(); break; case KoConnectionPoint::AlignBottomLeft: point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignBottomRight: point.position.rx() += shapeSize.width(); point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignCenter: point.position.rx() += 0.5 * shapeSize.width(); point.position.ry() += 0.5 * shapeSize.height(); break; } } // static QString KoShapePrivate::getStyleProperty(const char *property, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); QString value; if (styleStack.hasProperty(KoXmlNS::draw, property)) { value = styleStack.property(KoXmlNS::draw, property); } return value; } // ======== KoShape KoShape::KoShape() : d_ptr(new KoShapePrivate(this)) { notifyChanged(); } KoShape::KoShape(KoShapePrivate *dd) : d_ptr(dd) { } KoShape::~KoShape() { Q_D(KoShape); d->shapeChanged(Deleted); delete d_ptr; } KoShape *KoShape::cloneShape() const { return 0; } void KoShape::scale(qreal sx, qreal sy) { Q_D(KoShape); QPointF pos = position(); QTransform scaleMatrix; scaleMatrix.translate(pos.x(), pos.y()); scaleMatrix.scale(sx, sy); scaleMatrix.translate(-pos.x(), -pos.y()); d->localMatrix = d->localMatrix * scaleMatrix; notifyChanged(); d->shapeChanged(ScaleChanged); } void KoShape::rotate(qreal angle) { Q_D(KoShape); QPointF center = d->localMatrix.map(QPointF(0.5 * size().width(), 0.5 * size().height())); QTransform rotateMatrix; rotateMatrix.translate(center.x(), center.y()); rotateMatrix.rotate(angle); rotateMatrix.translate(-center.x(), -center.y()); d->localMatrix = d->localMatrix * rotateMatrix; notifyChanged(); d->shapeChanged(RotationChanged); } void KoShape::shear(qreal sx, qreal sy) { Q_D(KoShape); QPointF pos = position(); QTransform shearMatrix; shearMatrix.translate(pos.x(), pos.y()); shearMatrix.shear(sx, sy); shearMatrix.translate(-pos.x(), -pos.y()); d->localMatrix = d->localMatrix * shearMatrix; notifyChanged(); d->shapeChanged(ShearChanged); } void KoShape::setSize(const QSizeF &newSize) { Q_D(KoShape); QSizeF oldSize(size()); // always set size, as d->size and size() may vary d->size = newSize; if (oldSize == newSize) return; notifyChanged(); d->shapeChanged(SizeChanged); } void KoShape::setPosition(const QPointF &newPosition) { Q_D(KoShape); QPointF currentPos = position(); if (newPosition == currentPos) return; QTransform translateMatrix; translateMatrix.translate(newPosition.x() - currentPos.x(), newPosition.y() - currentPos.y()); d->localMatrix = d->localMatrix * translateMatrix; notifyChanged(); d->shapeChanged(PositionChanged); } bool KoShape::hitTest(const QPointF &position) const { Q_D(const KoShape); if (d->parent && d->parent->isClipped(this) && !d->parent->hitTest(position)) return false; QPointF point = absoluteTransformation(0).inverted().map(position); QRectF bb(QPointF(), size()); if (d->stroke) { KoInsets insets; d->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (bb.contains(point)) return true; // if there is no shadow we can as well just leave if (! d->shadow) return false; // the shadow has an offset to the shape, so we simply // check if the position minus the shadow offset hits the shape point = absoluteTransformation(0).inverted().map(position - d->shadow->offset()); return bb.contains(point); } QRectF KoShape::boundingRect() const { Q_D(const KoShape); QTransform transform = absoluteTransformation(0); QRectF bb = outlineRect(); if (d->stroke) { KoInsets insets; d->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } bb = transform.mapRect(bb); if (d->shadow) { KoInsets insets; d->shadow->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (d->filterEffectStack) { QRectF clipRect = d->filterEffectStack->clipRectForBoundingRect(outlineRect()); bb |= transform.mapRect(clipRect); } return bb; } +QRectF KoShape::boundingRect(const QList &shapes) +{ + QRectF boundingRect; + Q_FOREACH (KoShape *shape, shapes) { + boundingRect |= shape->boundingRect(); + } + return boundingRect; +} + QTransform KoShape::absoluteTransformation(const KoViewConverter *converter) const { Q_D(const KoShape); QTransform matrix; // apply parents matrix to inherit any transformations done there. KoShapeContainer * container = d->parent; if (container) { if (container->inheritsTransform(this)) { // We do need to pass the converter here, otherwise the parent's // translation is not inherited. matrix = container->absoluteTransformation(converter); } else { QSizeF containerSize = container->size(); QPointF containerPos = container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height()); if (converter) containerPos = converter->documentToView(containerPos); matrix.translate(containerPos.x(), containerPos.y()); } } if (converter) { QPointF pos = d->localMatrix.map(QPointF()); QPointF trans = converter->documentToView(pos) - pos; matrix.translate(trans.x(), trans.y()); } return d->localMatrix * matrix; } void KoShape::applyAbsoluteTransformation(const QTransform &matrix) { QTransform globalMatrix = absoluteTransformation(0); // the transformation is relative to the global coordinate system // but we want to change the local matrix, so convert the matrix // to be relative to the local coordinate system QTransform transformMatrix = globalMatrix * matrix * globalMatrix.inverted(); applyTransformation(transformMatrix); } void KoShape::applyTransformation(const QTransform &matrix) { Q_D(KoShape); d->localMatrix = matrix * d->localMatrix; notifyChanged(); d->shapeChanged(GenericMatrixChange); } void KoShape::setTransformation(const QTransform &matrix) { Q_D(KoShape); d->localMatrix = matrix; notifyChanged(); d->shapeChanged(GenericMatrixChange); } QTransform KoShape::transformation() const { Q_D(const KoShape); return d->localMatrix; } KoShape::ChildZOrderPolicy KoShape::childZOrderPolicy() { return ChildZDefault; } bool KoShape::compareShapeZIndex(KoShape *s1, KoShape *s2) { // First sort according to runThrough which is sort of a master level KoShape *parentShapeS1 = s1->parent(); KoShape *parentShapeS2 = s2->parent(); int runThrough1 = s1->runThrough(); int runThrough2 = s2->runThrough(); while (parentShapeS1) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough1 = parentShapeS1->runThrough(); } else { runThrough1 = runThrough1 + parentShapeS1->runThrough(); } parentShapeS1 = parentShapeS1->parent(); } while (parentShapeS2) { if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough2 = parentShapeS2->runThrough(); } else { runThrough2 = runThrough2 + parentShapeS2->runThrough(); } parentShapeS2 = parentShapeS2->parent(); } if (runThrough1 > runThrough2) { return false; } if (runThrough1 < runThrough2) { return true; } // If on the same runThrough level then the zIndex is all that matters. // // We basically walk up through the parents until we find a common base parent // To do that we need two loops where the inner loop walks up through the parents // of s2 every time we step up one parent level on s1 // // We don't update the index value until after we have seen that it's not a common base // That way we ensure that two children of a common base are sorted according to their respective // z value bool foundCommonParent = false; int index1 = s1->zIndex(); int index2 = s2->zIndex(); parentShapeS1 = s1; parentShapeS2 = s2; while (parentShapeS1 && !foundCommonParent) { parentShapeS2 = s2; index2 = parentShapeS2->zIndex(); while (parentShapeS2) { if (parentShapeS2 == parentShapeS1) { foundCommonParent = true; break; } if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { index2 = parentShapeS2->zIndex(); } parentShapeS2 = parentShapeS2->parent(); } if (!foundCommonParent) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { index1 = parentShapeS1->zIndex(); } parentShapeS1 = parentShapeS1->parent(); } } // If the one shape is a parent/child of the other then sort so. if (s1 == parentShapeS2) { return true; } if (s2 == parentShapeS1) { return false; } // If we went that far then the z-Index is used for sorting. return index1 < index2; } void KoShape::setParent(KoShapeContainer *parent) { Q_D(KoShape); if (d->parent == parent) { return; } KoShapeContainer *oldParent = d->parent; d->parent = 0; // avoids recursive removing if (oldParent) { oldParent->shapeInterface()->removeShape(this); } KIS_SAFE_ASSERT_RECOVER_NOOP(parent != this); if (parent && parent != this) { d->parent = parent; parent->shapeInterface()->addShape(this); } notifyChanged(); d->shapeChanged(ParentChanged); } int KoShape::zIndex() const { Q_D(const KoShape); return d->zIndex; } void KoShape::update() const { Q_D(const KoShape); if (!d->shapeManagers.empty()) { QRectF rect(boundingRect()); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->update(rect, this, true); } } } void KoShape::update(const QRectF &rect) const { if (rect.isEmpty() && !rect.isNull()) { return; } Q_D(const KoShape); if (!d->shapeManagers.empty() && isVisible()) { QRectF rc(absoluteTransformation(0).mapRect(rect)); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->update(rc); } } } QPainterPath KoShape::outline() const { QPainterPath path; path.addRect(outlineRect()); return path; } QRectF KoShape::outlineRect() const { const QSizeF s = size(); return QRectF(QPointF(0, 0), QSizeF(qMax(s.width(), qreal(0.0001)), qMax(s.height(), qreal(0.0001)))); } QPainterPath KoShape::shadowOutline() const { Q_D(const KoShape); if (d->fill) { return outline(); } return QPainterPath(); } QPointF KoShape::absolutePosition(KoFlake::AnchorPosition anchor) const { const QRectF rc = outlineRect(); QPointF point = rc.topLeft(); bool valid = false; QPointF anchoredPoint = KoFlake::anchorToPoint(anchor, rc, &valid); if (valid) { point = anchoredPoint; } return absoluteTransformation(0).map(point); } void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor) { Q_D(KoShape); QPointF currentAbsPosition = absolutePosition(anchor); QPointF translate = newPosition - currentAbsPosition; QTransform translateMatrix; translateMatrix.translate(translate.x(), translate.y()); applyAbsoluteTransformation(translateMatrix); notifyChanged(); d->shapeChanged(PositionChanged); } void KoShape::copySettings(const KoShape *shape) { Q_D(KoShape); d->size = shape->size(); d->connectors.clear(); Q_FOREACH (const KoConnectionPoint &point, shape->connectionPoints()) addConnectionPoint(point); d->zIndex = shape->zIndex(); d->visible = shape->isVisible(); // Ensure printable is true by default if (!d->visible) d->printable = true; else d->printable = shape->isPrintable(); d->geometryProtected = shape->isGeometryProtected(); d->protectContent = shape->isContentProtected(); d->selectable = shape->isSelectable(); d->keepAspect = shape->keepAspectRatio(); d->localMatrix = shape->d_ptr->localMatrix; } void KoShape::notifyChanged() { Q_D(KoShape); Q_FOREACH (KoShapeManager * manager, d->shapeManagers) { manager->notifyShapeChanged(this); } } void KoShape::setUserData(KoShapeUserData *userData) { Q_D(KoShape); d->userData.reset(userData); } KoShapeUserData *KoShape::userData() const { Q_D(const KoShape); return d->userData.data(); } bool KoShape::hasTransparency() const { Q_D(const KoShape); if (! d->fill) return true; else return d->fill->hasTransparency() || d->transparency > 0.0; } void KoShape::setTransparency(qreal transparency) { Q_D(KoShape); d->transparency = qBound(0.0, transparency, 1.0); d->shapeChanged(TransparencyChanged); notifyChanged(); } qreal KoShape::transparency(bool recursive) const { Q_D(const KoShape); if (!recursive || !parent()) { return d->transparency; } else { const qreal parentOpacity = 1.0-parent()->transparency(recursive); const qreal childOpacity = 1.0-d->transparency; return 1.0-(parentOpacity*childOpacity); } } KoInsets KoShape::strokeInsets() const { Q_D(const KoShape); KoInsets answer; if (d->stroke) d->stroke->strokeInsets(this, answer); return answer; } qreal KoShape::rotation() const { Q_D(const KoShape); // try to extract the rotation angle out of the local matrix // if it is a pure rotation matrix // check if the matrix has shearing mixed in if (fabs(fabs(d->localMatrix.m12()) - fabs(d->localMatrix.m21())) > 1e-10) return std::numeric_limits::quiet_NaN(); // check if the matrix has scaling mixed in if (fabs(d->localMatrix.m11() - d->localMatrix.m22()) > 1e-10) return std::numeric_limits::quiet_NaN(); // calculate the angle from the matrix elements qreal angle = atan2(-d->localMatrix.m21(), d->localMatrix.m11()) * 180.0 / M_PI; if (angle < 0.0) angle += 360.0; return angle; } QSizeF KoShape::size() const { Q_D(const KoShape); return d->size; } QPointF KoShape::position() const { Q_D(const KoShape); QPointF center = outlineRect().center(); return d->localMatrix.map(center) - center; } int KoShape::addConnectionPoint(const KoConnectionPoint &point) { Q_D(KoShape); // get next glue point id int nextConnectionPointId = KoConnectionPoint::FirstCustomConnectionPoint; if (d->connectors.size()) nextConnectionPointId = qMax(nextConnectionPointId, (--d->connectors.end()).key()+1); KoConnectionPoint p = point; d->convertFromShapeCoordinates(p, size()); d->connectors[nextConnectionPointId] = p; return nextConnectionPointId; } bool KoShape::setConnectionPoint(int connectionPointId, const KoConnectionPoint &point) { Q_D(KoShape); if (connectionPointId < 0) return false; const bool insertPoint = !hasConnectionPoint(connectionPointId); switch(connectionPointId) { case KoConnectionPoint::TopConnectionPoint: case KoConnectionPoint::RightConnectionPoint: case KoConnectionPoint::BottomConnectionPoint: case KoConnectionPoint::LeftConnectionPoint: { KoConnectionPoint::PointId id = static_cast(connectionPointId); d->connectors[id] = KoConnectionPoint::defaultConnectionPoint(id); break; } default: { KoConnectionPoint p = point; d->convertFromShapeCoordinates(p, size()); d->connectors[connectionPointId] = p; break; } } if(!insertPoint) d->shapeChanged(ConnectionPointChanged); return true; } bool KoShape::hasConnectionPoint(int connectionPointId) const { Q_D(const KoShape); return d->connectors.contains(connectionPointId); } KoConnectionPoint KoShape::connectionPoint(int connectionPointId) const { Q_D(const KoShape); KoConnectionPoint p = d->connectors.value(connectionPointId, KoConnectionPoint()); // convert glue point to shape coordinates d->convertToShapeCoordinates(p, size()); return p; } KoConnectionPoints KoShape::connectionPoints() const { Q_D(const KoShape); QSizeF s = size(); KoConnectionPoints points = d->connectors; KoConnectionPoints::iterator point = points.begin(); KoConnectionPoints::iterator lastPoint = points.end(); // convert glue points to shape coordinates for(; point != lastPoint; ++point) { d->convertToShapeCoordinates(point.value(), s); } return points; } void KoShape::removeConnectionPoint(int connectionPointId) { Q_D(KoShape); d->connectors.remove(connectionPointId); d->shapeChanged(ConnectionPointChanged); } void KoShape::clearConnectionPoints() { Q_D(KoShape); d->connectors.clear(); } KoShape::TextRunAroundSide KoShape::textRunAroundSide() const { Q_D(const KoShape); return d->textRunAroundSide; } void KoShape::setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrought) { Q_D(KoShape); if (side == RunThrough) { if (runThrought == Background) { setRunThrough(-1); } else { setRunThrough(1); } } else { setRunThrough(0); } if ( d->textRunAroundSide == side) { return; } d->textRunAroundSide = side; notifyChanged(); d->shapeChanged(TextRunAroundChanged); } qreal KoShape::textRunAroundDistanceTop() const { Q_D(const KoShape); return d->textRunAroundDistanceTop; } void KoShape::setTextRunAroundDistanceTop(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceTop = distance; } qreal KoShape::textRunAroundDistanceLeft() const { Q_D(const KoShape); return d->textRunAroundDistanceLeft; } void KoShape::setTextRunAroundDistanceLeft(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceLeft = distance; } qreal KoShape::textRunAroundDistanceRight() const { Q_D(const KoShape); return d->textRunAroundDistanceRight; } void KoShape::setTextRunAroundDistanceRight(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceRight = distance; } qreal KoShape::textRunAroundDistanceBottom() const { Q_D(const KoShape); return d->textRunAroundDistanceBottom; } void KoShape::setTextRunAroundDistanceBottom(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceBottom = distance; } qreal KoShape::textRunAroundThreshold() const { Q_D(const KoShape); return d->textRunAroundThreshold; } void KoShape::setTextRunAroundThreshold(qreal threshold) { Q_D(KoShape); d->textRunAroundThreshold = threshold; } KoShape::TextRunAroundContour KoShape::textRunAroundContour() const { Q_D(const KoShape); return d->textRunAroundContour; } void KoShape::setTextRunAroundContour(KoShape::TextRunAroundContour contour) { Q_D(KoShape); d->textRunAroundContour = contour; } void KoShape::setBackground(QSharedPointer fill) { Q_D(KoShape); d->fill = fill; d->shapeChanged(BackgroundChanged); notifyChanged(); } QSharedPointer KoShape::background() const { Q_D(const KoShape); return d->fill; } void KoShape::setZIndex(int zIndex) { Q_D(KoShape); if (d->zIndex == zIndex) return; d->zIndex = zIndex; notifyChanged(); } int KoShape::runThrough() { Q_D(const KoShape); return d->runThrough; } void KoShape::setRunThrough(short int runThrough) { Q_D(KoShape); d->runThrough = runThrough; } void KoShape::setVisible(bool on) { Q_D(KoShape); int _on = (on ? 1 : 0); if (d->visible == _on) return; d->visible = _on; } bool KoShape::isVisible(bool recursive) const { Q_D(const KoShape); if (! recursive) return d->visible; if (recursive && ! d->visible) return false; KoShapeContainer * parentShape = parent(); while (parentShape) { if (! parentShape->isVisible()) return false; parentShape = parentShape->parent(); } return true; } void KoShape::setPrintable(bool on) { Q_D(KoShape); d->printable = on; } bool KoShape::isPrintable() const { Q_D(const KoShape); if (d->visible) return d->printable; else return false; } void KoShape::setSelectable(bool selectable) { Q_D(KoShape); d->selectable = selectable; } bool KoShape::isSelectable() const { Q_D(const KoShape); return d->selectable; } void KoShape::setGeometryProtected(bool on) { Q_D(KoShape); d->geometryProtected = on; } bool KoShape::isGeometryProtected() const { Q_D(const KoShape); return d->geometryProtected; } void KoShape::setContentProtected(bool protect) { Q_D(KoShape); d->protectContent = protect; } bool KoShape::isContentProtected() const { Q_D(const KoShape); return d->protectContent; } KoShapeContainer *KoShape::parent() const { Q_D(const KoShape); return d->parent; } void KoShape::setKeepAspectRatio(bool keepAspect) { Q_D(KoShape); d->keepAspect = keepAspect; d->shapeChanged(KeepAspectRatioChange); notifyChanged(); } bool KoShape::keepAspectRatio() const { Q_D(const KoShape); return d->keepAspect; } QString KoShape::shapeId() const { Q_D(const KoShape); return d->shapeId; } void KoShape::setShapeId(const QString &id) { Q_D(KoShape); d->shapeId = id; } void KoShape::setCollisionDetection(bool detect) { Q_D(KoShape); d->detectCollision = detect; } bool KoShape::collisionDetection() { Q_D(KoShape); return d->detectCollision; } KoShapeStrokeModelSP KoShape::stroke() const { Q_D(const KoShape); return d->stroke; } void KoShape::setStroke(KoShapeStrokeModelSP stroke) { Q_D(KoShape); // TODO: check if it really updates stuff d->updateStroke(); d->stroke = stroke; d->updateStroke(); d->shapeChanged(StrokeChanged); notifyChanged(); } void KoShape::setShadow(KoShapeShadow *shadow) { Q_D(KoShape); if (d->shadow) d->shadow->deref(); d->shadow = shadow; if (d->shadow) { d->shadow->ref(); // TODO update changed area } d->shapeChanged(ShadowChanged); notifyChanged(); } KoShapeShadow *KoShape::shadow() const { Q_D(const KoShape); return d->shadow; } void KoShape::setBorder(KoBorder *border) { Q_D(KoShape); if (d->border) { // The shape owns the border. delete d->border; } d->border = border; d->shapeChanged(BorderChanged); notifyChanged(); } KoBorder *KoShape::border() const { Q_D(const KoShape); return d->border; } void KoShape::setClipPath(KoClipPath *clipPath) { Q_D(KoShape); d->clipPath.reset(clipPath); d->shapeChanged(ClipPathChanged); notifyChanged(); } KoClipPath * KoShape::clipPath() const { Q_D(const KoShape); return d->clipPath.data(); } void KoShape::setClipMask(KoClipMask *clipMask) { Q_D(KoShape); d->clipMask.reset(clipMask); } KoClipMask* KoShape::clipMask() const { Q_D(const KoShape); return d->clipMask.data(); } QTransform KoShape::transform() const { Q_D(const KoShape); return d->localMatrix; } QString KoShape::name() const { Q_D(const KoShape); return d->name; } void KoShape::setName(const QString &name) { Q_D(KoShape); d->name = name; } void KoShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const { Q_UNUSED(converter); Q_UNUSED(asynchronous); } bool KoShape::isEditable() const { Q_D(const KoShape); if (!d->visible || d->geometryProtected) return false; if (d->parent && d->parent->isChildLocked(this)) return false; return true; } // painting void KoShape::paintBorder(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(converter); KoBorder *bd = border(); if (!bd) { return; } QRectF borderRect = QRectF(QPointF(0, 0), size()); // Paint the border. bd->paint(painter, borderRect, KoBorder::PaintInsideLine); } // loading & saving methods QString KoShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { Q_D(const KoShape); // and fill the style KoShapeStrokeModelSP sm = stroke(); if (sm) { sm->fillStyle(style, context); } else { style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType); } KoShapeShadow *s = shadow(); if (s) s->fillStyle(style, context); QSharedPointer bg = background(); if (bg) { bg->fillStyle(style, context); } else { style.addProperty("draw:fill", "none", KoGenStyle::GraphicType); } KoBorder *b = border(); if (b) { b->saveOdf(style); } if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) { style.setAutoStyleInStylesDotXml(true); } QString value; if (isGeometryProtected()) { value = "position size"; } if (isContentProtected()) { if (! value.isEmpty()) value += ' '; value += "content"; } if (!value.isEmpty()) { style.addProperty("style:protect", value, KoGenStyle::GraphicType); } QMap::const_iterator it(d->additionalStyleAttributes.constBegin()); for (; it != d->additionalStyleAttributes.constEnd(); ++it) { style.addProperty(it.key(), it.value()); } if (parent() && parent()->isClipped(this)) { /* * In Calligra clipping is done using a parent shape which can be rotated, sheared etc * and even non-square. So the ODF interoperability version we write here is really * just a very simple version of that... */ qreal top = -position().y(); qreal left = -position().x(); qreal right = parent()->size().width() - size().width() - left; qreal bottom = parent()->size().height() - size().height() - top; style.addProperty("fo:clip", QString("rect(%1pt, %2pt, %3pt, %4pt)") .arg(top, 10, 'f').arg(right, 10, 'f') .arg(bottom, 10, 'f').arg(left, 10, 'f'), KoGenStyle::GraphicType); } QString wrap; switch (textRunAroundSide()) { case BiggestRunAroundSide: wrap = "biggest"; break; case LeftRunAroundSide: wrap = "left"; break; case RightRunAroundSide: wrap = "right"; break; case EnoughRunAroundSide: wrap = "dynamic"; break; case BothRunAroundSide: wrap = "parallel"; break; case NoRunAround: wrap = "none"; break; case RunThrough: wrap = "run-through"; break; } style.addProperty("style:wrap", wrap, KoGenStyle::GraphicType); switch (textRunAroundContour()) { case ContourBox: style.addProperty("style:wrap-contour", "false", KoGenStyle::GraphicType); break; case ContourFull: style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType); style.addProperty("style:wrap-contour-mode", "full", KoGenStyle::GraphicType); break; case ContourOutside: style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType); style.addProperty("style:wrap-contour-mode", "outside", KoGenStyle::GraphicType); break; } style.addPropertyPt("style:wrap-dynamic-threshold", textRunAroundThreshold(), KoGenStyle::GraphicType); if ((textRunAroundDistanceLeft() == textRunAroundDistanceRight()) && (textRunAroundDistanceTop() == textRunAroundDistanceBottom()) && (textRunAroundDistanceLeft() == textRunAroundDistanceTop())) { style.addPropertyPt("fo:margin", textRunAroundDistanceLeft(), KoGenStyle::GraphicType); } else { style.addPropertyPt("fo:margin-left", textRunAroundDistanceLeft(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-top", textRunAroundDistanceTop(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-right", textRunAroundDistanceRight(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-bottom", textRunAroundDistanceBottom(), KoGenStyle::GraphicType); } return context.mainStyles().insert(style, context.isSet(KoShapeSavingContext::PresentationShape) ? "pr" : "gr"); } void KoShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_D(KoShape); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); d->fill.clear(); d->stroke.clear(); if (d->shadow && !d->shadow->deref()) { delete d->shadow; d->shadow = 0; } setBackground(loadOdfFill(context)); setStroke(loadOdfStroke(element, context)); setShadow(d->loadOdfShadow(context)); setBorder(d->loadOdfBorder(context)); QString protect(styleStack.property(KoXmlNS::style, "protect")); setGeometryProtected(protect.contains("position") || protect.contains("size")); setContentProtected(protect.contains("content")); QString margin = styleStack.property(KoXmlNS::fo, "margin"); if (!margin.isEmpty()) { setTextRunAroundDistanceLeft(KoUnit::parseValue(margin)); setTextRunAroundDistanceTop(KoUnit::parseValue(margin)); setTextRunAroundDistanceRight(KoUnit::parseValue(margin)); setTextRunAroundDistanceBottom(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-left"); if (!margin.isEmpty()) { setTextRunAroundDistanceLeft(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-top"); if (!margin.isEmpty()) { setTextRunAroundDistanceTop(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-right"); if (!margin.isEmpty()) { setTextRunAroundDistanceRight(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-bottom"); if (!margin.isEmpty()) { setTextRunAroundDistanceBottom(KoUnit::parseValue(margin)); } QString wrap; if (styleStack.hasProperty(KoXmlNS::style, "wrap")) { wrap = styleStack.property(KoXmlNS::style, "wrap"); } else { // no value given in the file, but guess biggest wrap = "biggest"; } if (wrap == "none") { setTextRunAroundSide(KoShape::NoRunAround); } else if (wrap == "run-through") { QString runTrought = styleStack.property(KoXmlNS::style, "run-through", "background"); if (runTrought == "background") { setTextRunAroundSide(KoShape::RunThrough, KoShape::Background); } else { setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground); } } else { if (wrap == "biggest") setTextRunAroundSide(KoShape::BiggestRunAroundSide); else if (wrap == "left") setTextRunAroundSide(KoShape::LeftRunAroundSide); else if (wrap == "right") setTextRunAroundSide(KoShape::RightRunAroundSide); else if (wrap == "dynamic") setTextRunAroundSide(KoShape::EnoughRunAroundSide); else if (wrap == "parallel") setTextRunAroundSide(KoShape::BothRunAroundSide); } if (styleStack.hasProperty(KoXmlNS::style, "wrap-dynamic-threshold")) { QString wrapThreshold = styleStack.property(KoXmlNS::style, "wrap-dynamic-threshold"); if (!wrapThreshold.isEmpty()) { setTextRunAroundThreshold(KoUnit::parseValue(wrapThreshold)); } } if (styleStack.property(KoXmlNS::style, "wrap-contour", "false") == "true") { if (styleStack.property(KoXmlNS::style, "wrap-contour-mode", "full") == "full") { setTextRunAroundContour(KoShape::ContourFull); } else { setTextRunAroundContour(KoShape::ContourOutside); } } else { setTextRunAroundContour(KoShape::ContourBox); } } bool KoShape::loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes) { if (attributes & OdfPosition) { QPointF pos(position()); if (element.hasAttributeNS(KoXmlNS::svg, "x")) pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); if (element.hasAttributeNS(KoXmlNS::svg, "y")) pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); setPosition(pos); } if (attributes & OdfSize) { QSizeF s(size()); if (element.hasAttributeNS(KoXmlNS::svg, "width")) s.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); if (element.hasAttributeNS(KoXmlNS::svg, "height")) s.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); setSize(s); } if (attributes & OdfLayer) { if (element.hasAttributeNS(KoXmlNS::draw, "layer")) { KoShapeLayer *layer = context.layer(element.attributeNS(KoXmlNS::draw, "layer")); if (layer) { setParent(layer); } } } if (attributes & OdfId) { KoElementReference ref; ref.loadOdf(element); if (ref.isValid()) { context.addShapeId(this, ref.toString()); } } if (attributes & OdfZIndex) { if (element.hasAttributeNS(KoXmlNS::draw, "z-index")) { setZIndex(element.attributeNS(KoXmlNS::draw, "z-index").toInt()); } else { setZIndex(context.zIndex()); } } if (attributes & OdfName) { if (element.hasAttributeNS(KoXmlNS::draw, "name")) { setName(element.attributeNS(KoXmlNS::draw, "name")); } } if (attributes & OdfStyle) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.save(); if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic"); } if (element.hasAttributeNS(KoXmlNS::presentation, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::presentation, "style-name", "presentation"); } loadStyle(element, context); styleStack.restore(); } if (attributes & OdfTransformation) { QString transform = element.attributeNS(KoXmlNS::draw, "transform", QString()); if (! transform.isEmpty()) applyAbsoluteTransformation(parseOdfTransform(transform)); } if (attributes & OdfAdditionalAttributes) { QSet additionalAttributeData = KoShapeLoadingContext::additionalAttributeData(); Q_FOREACH (const KoShapeLoadingContext::AdditionalAttributeData &attributeData, additionalAttributeData) { if (element.hasAttributeNS(attributeData.ns, attributeData.tag)) { QString value = element.attributeNS(attributeData.ns, attributeData.tag); //debugFlake << "load additional attribute" << attributeData.tag << value; setAdditionalAttribute(attributeData.name, value); } } } if (attributes & OdfCommonChildElements) { // load glue points (connection points) loadOdfGluePoints(element, context); } return true; } QSharedPointer KoShape::loadOdfFill(KoShapeLoadingContext &context) const { QString fill = KoShapePrivate::getStyleProperty("fill", context); QSharedPointer bg; if (fill == "solid") { bg = QSharedPointer(new KoColorBackground()); } else if (fill == "hatch") { bg = QSharedPointer(new KoHatchBackground()); } else if (fill == "gradient") { QString styleName = KoShapePrivate::getStyleProperty("fill-gradient-name", context); KoXmlElement *e = context.odfLoadingContext().stylesReader().drawStyles("gradient")[styleName]; QString style; if (e) { style = e->attributeNS(KoXmlNS::draw, "style", QString()); } if ((style == "rectangular") || (style == "square")) { bg = QSharedPointer(new KoOdfGradientBackground()); } else { QGradient *gradient = new QLinearGradient(); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); bg = QSharedPointer(new KoGradientBackground(gradient)); } } else if (fill == "bitmap") { bg = QSharedPointer(new KoPatternBackground(context.imageCollection())); #ifndef NWORKAROUND_ODF_BUGS } else if (fill.isEmpty()) { bg = QSharedPointer(KoOdfWorkaround::fixBackgroundColor(this, context)); return bg; #endif } else { return QSharedPointer(0); } if (!bg->loadStyle(context.odfLoadingContext(), size())) { return QSharedPointer(0); } return bg; } KoShapeStrokeModelSP KoShape::loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader(); QString stroke = KoShapePrivate::getStyleProperty("stroke", context); if (stroke == "solid" || stroke == "dash") { QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke, stylesReader); QSharedPointer stroke(new KoShapeStroke()); if (styleStack.hasProperty(KoXmlNS::calligra, "stroke-gradient")) { QString gradientName = styleStack.property(KoXmlNS::calligra, "stroke-gradient"); QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyleByName(stylesReader, gradientName, size()); stroke->setLineBrush(brush); } else { stroke->setColor(pen.color()); } #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPenWidth(pen, context); #endif stroke->setLineWidth(pen.widthF()); stroke->setJoinStyle(pen.joinStyle()); stroke->setLineStyle(pen.style(), pen.dashPattern()); stroke->setCapStyle(pen.capStyle()); return stroke; #ifndef NWORKAROUND_ODF_BUGS } else if (stroke.isEmpty()) { QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, "solid", stylesReader); if (KoOdfWorkaround::fixMissingStroke(pen, element, context, this)) { QSharedPointer stroke(new KoShapeStroke()); #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPenWidth(pen, context); #endif stroke->setLineWidth(pen.widthF()); stroke->setJoinStyle(pen.joinStyle()); stroke->setLineStyle(pen.style(), pen.dashPattern()); stroke->setCapStyle(pen.capStyle()); stroke->setColor(pen.color()); return stroke; } #endif } return KoShapeStrokeModelSP(); } KoShapeShadow *KoShapePrivate::loadOdfShadow(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); QString shadowStyle = KoShapePrivate::getStyleProperty("shadow", context); if (shadowStyle == "visible" || shadowStyle == "hidden") { KoShapeShadow *shadow = new KoShapeShadow(); QColor shadowColor(styleStack.property(KoXmlNS::draw, "shadow-color")); qreal offsetX = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-x")); qreal offsetY = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-y")); shadow->setOffset(QPointF(offsetX, offsetY)); qreal blur = KoUnit::parseValue(styleStack.property(KoXmlNS::calligra, "shadow-blur-radius")); shadow->setBlur(blur); QString opacity = styleStack.property(KoXmlNS::draw, "shadow-opacity"); if (! opacity.isEmpty() && opacity.right(1) == "%") shadowColor.setAlphaF(opacity.left(opacity.length() - 1).toFloat() / 100.0); shadow->setColor(shadowColor); shadow->setVisible(shadowStyle == "visible"); return shadow; } return 0; } KoBorder *KoShapePrivate::loadOdfBorder(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoBorder *border = new KoBorder(); if (border->loadOdf(styleStack)) { return border; } delete border; return 0; } void KoShape::loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_D(KoShape); KoXmlElement child; bool hasCenterGluePoint = false; forEachElement(child, element) { if (child.namespaceURI() != KoXmlNS::draw) continue; if (child.localName() != "glue-point") continue; // NOTE: this uses draw:id, but apparently while ODF 1.2 has deprecated // all use of draw:id for xml:id, it didn't specify that here, so it // doesn't support xml:id (and so, maybe, shouldn't use KoElementReference. const QString id = child.attributeNS(KoXmlNS::draw, "id", QString()); const int index = id.toInt(); // connection point in center should be default but odf doesn't support, // in new shape, first custom point is in center, it's okay to replace that point // with point from xml now, we'll add it back later if(id.isEmpty() || index < KoConnectionPoint::FirstCustomConnectionPoint || (index != KoConnectionPoint::FirstCustomConnectionPoint && d->connectors.contains(index))) { warnFlake << "glue-point with no or invalid id"; continue; } QString xStr = child.attributeNS(KoXmlNS::svg, "x", QString()).simplified(); QString yStr = child.attributeNS(KoXmlNS::svg, "y", QString()).simplified(); if(xStr.isEmpty() || yStr.isEmpty()) { warnFlake << "glue-point with invald position"; continue; } KoConnectionPoint connector; const QString align = child.attributeNS(KoXmlNS::draw, "align", QString()); if (align.isEmpty()) { #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixGluePointPosition(xStr, context); KoOdfWorkaround::fixGluePointPosition(yStr, context); #endif if(!xStr.endsWith('%') || !yStr.endsWith('%')) { warnFlake << "glue-point with invald position"; continue; } // x and y are relative to drawing object center connector.position.setX(xStr.remove('%').toDouble()/100.0); connector.position.setY(yStr.remove('%').toDouble()/100.0); // convert position to be relative to top-left corner connector.position += QPointF(0.5, 0.5); connector.position.rx() = qBound(0.0, connector.position.x(), 1.0); connector.position.ry() = qBound(0.0, connector.position.y(), 1.0); } else { // absolute distances to the edge specified by align connector.position.setX(KoUnit::parseValue(xStr)); connector.position.setY(KoUnit::parseValue(yStr)); if (align == "top-left") { connector.alignment = KoConnectionPoint::AlignTopLeft; } else if (align == "top") { connector.alignment = KoConnectionPoint::AlignTop; } else if (align == "top-right") { connector.alignment = KoConnectionPoint::AlignTopRight; } else if (align == "left") { connector.alignment = KoConnectionPoint::AlignLeft; } else if (align == "center") { connector.alignment = KoConnectionPoint::AlignCenter; } else if (align == "right") { connector.alignment = KoConnectionPoint::AlignRight; } else if (align == "bottom-left") { connector.alignment = KoConnectionPoint::AlignBottomLeft; } else if (align == "bottom") { connector.alignment = KoConnectionPoint::AlignBottom; } else if (align == "bottom-right") { connector.alignment = KoConnectionPoint::AlignBottomRight; } debugFlake << "using alignment" << align; } const QString escape = child.attributeNS(KoXmlNS::draw, "escape-direction", QString()); if (!escape.isEmpty()) { if (escape == "horizontal") { connector.escapeDirection = KoConnectionPoint::HorizontalDirections; } else if (escape == "vertical") { connector.escapeDirection = KoConnectionPoint::VerticalDirections; } else if (escape == "left") { connector.escapeDirection = KoConnectionPoint::LeftDirection; } else if (escape == "right") { connector.escapeDirection = KoConnectionPoint::RightDirection; } else if (escape == "up") { connector.escapeDirection = KoConnectionPoint::UpDirection; } else if (escape == "down") { connector.escapeDirection = KoConnectionPoint::DownDirection; } debugFlake << "using escape direction" << escape; } d->connectors[index] = connector; debugFlake << "loaded glue-point" << index << "at position" << connector.position; if (d->connectors[index].position == QPointF(0.5, 0.5)) { hasCenterGluePoint = true; debugFlake << "center glue-point found at id " << index; } } if (!hasCenterGluePoint) { d->connectors[d->connectors.count()] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter); } debugFlake << "shape has now" << d->connectors.count() << "glue-points"; } void KoShape::loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor) { Q_D(KoShape); KoXmlElement child; forEachElement(child, element) { if (child.namespaceURI() != KoXmlNS::draw) continue; if (child.localName() != "contour-polygon") continue; debugFlake << "shape loads contour-polygon"; KoPathShape *ps = new KoPathShape(); ps->loadContourOdf(child, context, scaleFactor); ps->setTransformation(transformation()); KoClipPath *clipPath = new KoClipPath({ps}, KoFlake::UserSpaceOnUse); d->clipPath.reset(clipPath); } } QTransform KoShape::parseOdfTransform(const QString &transform) { QTransform matrix; // Split string for handling 1 transform statement at a time QStringList subtransforms = transform.split(')', QString::SkipEmptyParts); QStringList::ConstIterator it = subtransforms.constBegin(); QStringList::ConstIterator end = subtransforms.constEnd(); for (; it != end; ++it) { QStringList subtransform = (*it).split('(', QString::SkipEmptyParts); subtransform[0] = subtransform[0].trimmed().toLower(); subtransform[1] = subtransform[1].simplified(); QRegExp reg("[,( ]"); QStringList params = subtransform[1].split(reg, QString::SkipEmptyParts); if (subtransform[0].startsWith(';') || subtransform[0].startsWith(',')) subtransform[0] = subtransform[0].right(subtransform[0].length() - 1); QString cmd = subtransform[0].toLower(); if (cmd == "rotate") { QTransform rotMatrix; if (params.count() == 3) { qreal x = KoUnit::parseValue(params[1]); qreal y = KoUnit::parseValue(params[2]); rotMatrix.translate(x, y); // oo2 rotates by radians rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI); rotMatrix.translate(-x, -y); } else { // oo2 rotates by radians rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI); } matrix = matrix * rotMatrix; } else if (cmd == "translate") { QTransform moveMatrix; if (params.count() == 2) { qreal x = KoUnit::parseValue(params[0]); qreal y = KoUnit::parseValue(params[1]); moveMatrix.translate(x, y); } else // Spec : if only one param given, assume 2nd param to be 0 moveMatrix.translate(KoUnit::parseValue(params[0]) , 0); matrix = matrix * moveMatrix; } else if (cmd == "scale") { QTransform scaleMatrix; if (params.count() == 2) scaleMatrix.scale(params[0].toDouble(), params[1].toDouble()); else // Spec : if only one param given, assume uniform scaling scaleMatrix.scale(params[0].toDouble(), params[0].toDouble()); matrix = matrix * scaleMatrix; } else if (cmd == "skewx") { QPointF p = absolutePosition(KoFlake::TopLeft); QTransform shearMatrix; shearMatrix.translate(p.x(), p.y()); shearMatrix.shear(tan(-params[0].toDouble()), 0.0F); shearMatrix.translate(-p.x(), -p.y()); matrix = matrix * shearMatrix; } else if (cmd == "skewy") { QPointF p = absolutePosition(KoFlake::TopLeft); QTransform shearMatrix; shearMatrix.translate(p.x(), p.y()); shearMatrix.shear(0.0F, tan(-params[0].toDouble())); shearMatrix.translate(-p.x(), -p.y()); matrix = matrix * shearMatrix; } else if (cmd == "matrix") { QTransform m; if (params.count() >= 6) { m.setMatrix(params[0].toDouble(), params[1].toDouble(), 0, params[2].toDouble(), params[3].toDouble(), 0, KoUnit::parseValue(params[4]), KoUnit::parseValue(params[5]), 1); } matrix = matrix * m; } } return matrix; } void KoShape::saveOdfAttributes(KoShapeSavingContext &context, int attributes) const { Q_D(const KoShape); if (attributes & OdfStyle) { KoGenStyle style; // all items that should be written to 'draw:frame' and any other 'draw:' object that inherits this shape if (context.isSet(KoShapeSavingContext::PresentationShape)) { style = KoGenStyle(KoGenStyle::PresentationAutoStyle, "presentation"); context.xmlWriter().addAttribute("presentation:style-name", saveStyle(style, context)); } else { style = KoGenStyle(KoGenStyle::GraphicAutoStyle, "graphic"); context.xmlWriter().addAttribute("draw:style-name", saveStyle(style, context)); } } if (attributes & OdfId) { if (context.isSet(KoShapeSavingContext::DrawId)) { KoElementReference ref = context.xmlid(this, "shape", KoElementReference::Counter); ref.saveOdf(&context.xmlWriter(), KoElementReference::DrawId); } } if (attributes & OdfName) { if (! name().isEmpty()) context.xmlWriter().addAttribute("draw:name", name()); } if (attributes & OdfLayer) { KoShape *parent = d->parent; while (parent) { if (dynamic_cast(parent)) { context.xmlWriter().addAttribute("draw:layer", parent->name()); break; } parent = parent->parent(); } } if (attributes & OdfZIndex && context.isSet(KoShapeSavingContext::ZIndex)) { context.xmlWriter().addAttribute("draw:z-index", zIndex()); } if (attributes & OdfSize) { QSizeF s(size()); if (parent() && parent()->isClipped(this)) { // being clipped shrinks our visible size // clipping in ODF is done using a combination of visual size and content cliprect. // A picture of 10cm x 10cm displayed in a box of 2cm x 4cm will be scaled (out // of proportion in this case). If we then add a fo:clip like; // fo:clip="rect(2cm, 3cm, 4cm, 5cm)" (top, right, bottom, left) // our original 10x10 is clipped to 2cm x 4cm and *then* fitted in that box. // TODO do this properly by subtracting rects s = parent()->size(); } context.xmlWriter().addAttributePt("svg:width", s.width()); context.xmlWriter().addAttributePt("svg:height", s.height()); } // The position is implicitly stored in the transformation matrix // if the transformation is saved as well if ((attributes & OdfPosition) && !(attributes & OdfTransformation)) { const QPointF p(position() * context.shapeOffset(this)); context.xmlWriter().addAttributePt("svg:x", p.x()); context.xmlWriter().addAttributePt("svg:y", p.y()); } if (attributes & OdfTransformation) { QTransform matrix = absoluteTransformation(0) * context.shapeOffset(this); if (! matrix.isIdentity()) { if (qAbs(matrix.m11() - 1) < 1E-5 // 1 && qAbs(matrix.m12()) < 1E-5 // 0 && qAbs(matrix.m21()) < 1E-5 // 0 && qAbs(matrix.m22() - 1) < 1E-5) { // 1 context.xmlWriter().addAttributePt("svg:x", matrix.dx()); context.xmlWriter().addAttributePt("svg:y", matrix.dy()); } else { QString m = QString("matrix(%1 %2 %3 %4 %5pt %6pt)") .arg(matrix.m11(), 0, 'f', 11) .arg(matrix.m12(), 0, 'f', 11) .arg(matrix.m21(), 0, 'f', 11) .arg(matrix.m22(), 0, 'f', 11) .arg(matrix.dx(), 0, 'f', 11) .arg(matrix.dy(), 0, 'f', 11); context.xmlWriter().addAttribute("draw:transform", m); } } } if (attributes & OdfViewbox) { const QSizeF s(size()); QString viewBox = QString("0 0 %1 %2").arg(qRound(s.width())).arg(qRound(s.height())); context.xmlWriter().addAttribute("svg:viewBox", viewBox); } if (attributes & OdfAdditionalAttributes) { QMap::const_iterator it(d->additionalAttributes.constBegin()); for (; it != d->additionalAttributes.constEnd(); ++it) { context.xmlWriter().addAttribute(it.key().toUtf8(), it.value()); } } } void KoShape::saveOdfCommonChildElements(KoShapeSavingContext &context) const { Q_D(const KoShape); // save glue points see ODF 9.2.19 Glue Points if(d->connectors.count()) { KoConnectionPoints::const_iterator cp = d->connectors.constBegin(); KoConnectionPoints::const_iterator lastCp = d->connectors.constEnd(); for(; cp != lastCp; ++cp) { // do not save default glue points if(cp.key() < 4) continue; context.xmlWriter().startElement("draw:glue-point"); context.xmlWriter().addAttribute("draw:id", QString("%1").arg(cp.key())); if (cp.value().alignment == KoConnectionPoint::AlignNone) { // convert to percent from center const qreal x = cp.value().position.x() * 100.0 - 50.0; const qreal y = cp.value().position.y() * 100.0 - 50.0; context.xmlWriter().addAttribute("svg:x", QString("%1%").arg(x)); context.xmlWriter().addAttribute("svg:y", QString("%1%").arg(y)); } else { context.xmlWriter().addAttributePt("svg:x", cp.value().position.x()); context.xmlWriter().addAttributePt("svg:y", cp.value().position.y()); } QString escapeDirection; switch(cp.value().escapeDirection) { case KoConnectionPoint::HorizontalDirections: escapeDirection = "horizontal"; break; case KoConnectionPoint::VerticalDirections: escapeDirection = "vertical"; break; case KoConnectionPoint::LeftDirection: escapeDirection = "left"; break; case KoConnectionPoint::RightDirection: escapeDirection = "right"; break; case KoConnectionPoint::UpDirection: escapeDirection = "up"; break; case KoConnectionPoint::DownDirection: escapeDirection = "down"; break; default: // fall through break; } if(!escapeDirection.isEmpty()) { context.xmlWriter().addAttribute("draw:escape-direction", escapeDirection); } QString alignment; switch(cp.value().alignment) { case KoConnectionPoint::AlignTopLeft: alignment = "top-left"; break; case KoConnectionPoint::AlignTop: alignment = "top"; break; case KoConnectionPoint::AlignTopRight: alignment = "top-right"; break; case KoConnectionPoint::AlignLeft: alignment = "left"; break; case KoConnectionPoint::AlignCenter: alignment = "center"; break; case KoConnectionPoint::AlignRight: alignment = "right"; break; case KoConnectionPoint::AlignBottomLeft: alignment = "bottom-left"; break; case KoConnectionPoint::AlignBottom: alignment = "bottom"; break; case KoConnectionPoint::AlignBottomRight: alignment = "bottom-right"; break; default: // fall through break; } if(!alignment.isEmpty()) { context.xmlWriter().addAttribute("draw:align", alignment); } context.xmlWriter().endElement(); } } } void KoShape::saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const { Q_D(const KoShape); debugFlake << "shape saves contour-polygon"; if (d->clipPath && !d->clipPath->clipPathShapes().isEmpty()) { // This will loose data as odf can only save one set of contour wheras // svg loading and at least karbon editing can produce more than one // TODO, FIXME see if we can save more than one clipshape to odf d->clipPath->clipPathShapes().first()->saveContourOdf(context, originalSize); } } // end loading & saving methods // static void KoShape::applyConversion(QPainter &painter, const KoViewConverter &converter) { qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); painter.scale(zoomX, zoomY); } KisHandlePainterHelper KoShape::createHandlePainterHelper(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius) { const QTransform originalPainterTransform = painter->transform(); painter->setTransform(shape->absoluteTransformation(&converter) * painter->transform()); KoShape::applyConversion(*painter, converter); // move c-tor return KisHandlePainterHelper(painter, originalPainterTransform, handleRadius); } QPointF KoShape::shapeToDocument(const QPointF &point) const { return absoluteTransformation(0).map(point); } QRectF KoShape::shapeToDocument(const QRectF &rect) const { return absoluteTransformation(0).mapRect(rect); } QPointF KoShape::documentToShape(const QPointF &point) const { return absoluteTransformation(0).inverted().map(point); } QRectF KoShape::documentToShape(const QRectF &rect) const { return absoluteTransformation(0).inverted().mapRect(rect); } bool KoShape::addDependee(KoShape *shape) { Q_D(KoShape); if (! shape) return false; // refuse to establish a circular dependency if (shape->hasDependee(this)) return false; if (! d->dependees.contains(shape)) d->dependees.append(shape); return true; } void KoShape::removeDependee(KoShape *shape) { Q_D(KoShape); int index = d->dependees.indexOf(shape); if (index >= 0) d->dependees.removeAt(index); } bool KoShape::hasDependee(KoShape *shape) const { Q_D(const KoShape); return d->dependees.contains(shape); } QList KoShape::dependees() const { Q_D(const KoShape); return d->dependees; } void KoShape::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(type); Q_UNUSED(shape); } KoSnapData KoShape::snapData() const { return KoSnapData(); } void KoShape::setAdditionalAttribute(const QString &name, const QString &value) { Q_D(KoShape); d->additionalAttributes.insert(name, value); } void KoShape::removeAdditionalAttribute(const QString &name) { Q_D(KoShape); d->additionalAttributes.remove(name); } bool KoShape::hasAdditionalAttribute(const QString &name) const { Q_D(const KoShape); return d->additionalAttributes.contains(name); } QString KoShape::additionalAttribute(const QString &name) const { Q_D(const KoShape); return d->additionalAttributes.value(name); } void KoShape::setAdditionalStyleAttribute(const char *name, const QString &value) { Q_D(KoShape); d->additionalStyleAttributes.insert(name, value); } void KoShape::removeAdditionalStyleAttribute(const char *name) { Q_D(KoShape); d->additionalStyleAttributes.remove(name); } KoFilterEffectStack *KoShape::filterEffectStack() const { Q_D(const KoShape); return d->filterEffectStack; } void KoShape::setFilterEffectStack(KoFilterEffectStack *filterEffectStack) { Q_D(KoShape); if (d->filterEffectStack) d->filterEffectStack->deref(); d->filterEffectStack = filterEffectStack; if (d->filterEffectStack) { d->filterEffectStack->ref(); } notifyChanged(); } QSet KoShape::toolDelegates() const { Q_D(const KoShape); return d->toolDelegates; } void KoShape::setToolDelegates(const QSet &delegates) { Q_D(KoShape); d->toolDelegates = delegates; } QString KoShape::hyperLink () const { Q_D(const KoShape); return d->hyperLink; } void KoShape::setHyperLink(const QString &hyperLink) { Q_D(KoShape); d->hyperLink = hyperLink; } KoShapePrivate *KoShape::priv() { Q_D(KoShape); return d; } KoShape::ShapeChangeListener::~ShapeChangeListener() { Q_FOREACH(KoShape *shape, m_registeredShapes) { shape->removeShapeChangeListener(this); } } void KoShape::ShapeChangeListener::registerShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(!m_registeredShapes.contains(shape)); m_registeredShapes.append(shape); } void KoShape::ShapeChangeListener::unregisterShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape)); m_registeredShapes.removeAll(shape); } void KoShape::ShapeChangeListener::notifyShapeChangedImpl(KoShape::ChangeType type, KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape)); notifyShapeChanged(type, shape); if (type == KoShape::Deleted) { unregisterShape(shape); } } void KoShape::addShapeChangeListener(KoShape::ShapeChangeListener *listener) { Q_D(KoShape); KIS_SAFE_ASSERT_RECOVER_RETURN(!d->listeners.contains(listener)); listener->registerShape(this); d->listeners.append(listener); } void KoShape::removeShapeChangeListener(KoShape::ShapeChangeListener *listener) { Q_D(KoShape); KIS_SAFE_ASSERT_RECOVER_RETURN(d->listeners.contains(listener)); d->listeners.removeAll(listener); listener->unregisterShape(this); } QList KoShape::linearizeSubtree(const QList &shapes) { QList result; Q_FOREACH (KoShape *shape, shapes) { result << shape; KoShapeContainer *container = dynamic_cast(shape); if (container) { result << linearizeSubtree(container->shapes()); } } return result; } diff --git a/libs/flake/KoShape.h b/libs/flake/KoShape.h index 4e2699fbcb..bee698062c 100644 --- a/libs/flake/KoShape.h +++ b/libs/flake/KoShape.h @@ -1,1222 +1,1228 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006, 2008 C. Boemann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2007-2009,2011 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPE_H #define KOSHAPE_H #include "KoFlake.h" #include "KoFlakeTypes.h" #include "KoConnectionPoint.h" #include #include #include #include #include "kritaflake_export.h" class QPainter; class QRectF; class QPainterPath; class QTransform; class KoShapeContainer; class KoShapeStrokeModel; class KoShapeUserData; class KoViewConverter; class KoShapeApplicationData; class KoShapeSavingContext; class KoShapeLoadingContext; class KoGenStyle; class KoShapeShadow; class KoShapePrivate; class KoFilterEffectStack; class KoSnapData; class KoClipPath; class KoClipMask; class KoShapePaintingContext; class KoShapeAnchor; class KoBorder; struct KoInsets; class KoShapeBackground; class KisHandlePainterHelper; /** * * Base class for all flake shapes. Shapes extend this class * to allow themselves to be manipulated. This class just represents * a graphical shape in the document and can be manipulated by some default * tools in this library. * * Due to the limited responsibility of this class, the extending object * can have any data backend and is responsible for painting itself. * * We strongly suggest that any extending class will use a Model View * Controller (MVC) design where the View part is all in this class, as well * as the one that inherits from this one. This allows the data that rests * in the model to be reused in different parts of the document. For example * by having two flake objects that show that same data. Or each showing a section of it. * * The KoShape data is completely in postscript-points (pt) (see KoUnit * for conversion methods to and from points). * This image will explain the real-world use of the shape and its options. *
* The Rotation center can be returned with absolutePosition() * *

Flake objects can be created in three ways: *

    *
  • a simple new KoDerivedFlake(), *
  • through an associated tool, *
  • through a factory *
* *

Shape interaction notifications

* We had several notification methods that allow your shape to be notified of changes in other * shapes positions or rotation etc. *
  1. The most general is KoShape::shapeChanged().
    * a virtual method that you can use to check various changed to your shape made by tools or otherwise.
  2. *
  3. for shape hierarchies the parent may receive a notification when a child was modified. * This is done though KoShapeContainerModel::childChanged()
  4. *
  5. any shape that is at a similar position as another shape there is collision detection. * You can register your shape to be sensitive to any changes like moving or whatever to * other shapes that intersect yours. * Such changes will then be notified to your shape using the method from (1) You should call * KoShape::setCollisionDetection(bool) to enable this. *
*/ class KRITAFLAKE_EXPORT KoShape { public: /// Used by shapeChanged() to select which change was made enum ChangeType { PositionChanged, ///< used after a setPosition() RotationChanged, ///< used after a setRotation() ScaleChanged, ///< used after a scale() ShearChanged, ///< used after a shear() SizeChanged, ///< used after a setSize() GenericMatrixChange, ///< used after the matrix was changed without knowing which property explicitly changed KeepAspectRatioChange, ///< used after setKeepAspectRatio() ParentChanged, ///< used after a setParent() CollisionDetected, ///< used when another shape moved in our boundingrect Deleted, ///< the shape was deleted StrokeChanged, ///< the shapes stroke has changed BackgroundChanged, ///< the shapes background has changed ShadowChanged, ///< the shapes shadow has changed BorderChanged, ///< the shapes border has changed ParameterChanged, ///< the shapes parameter has changed (KoParameterShape only) ContentChanged, ///< the content of the shape changed e.g. a new image inside a pixmap/text change inside a textshape TextRunAroundChanged, ///< used after a setTextRunAroundSide() ChildChanged, ///< a child of a container was changed/removed. This is propagated to all parents ConnectionPointChanged, ///< a connection point has changed ClipPathChanged, ///< the shapes clip path has changed TransparencyChanged ///< the shapetransparency value has changed }; /// The behavior text should do when intersecting this shape. enum TextRunAroundSide { BiggestRunAroundSide, ///< Run other text around the side that has the most space LeftRunAroundSide, ///< Run other text around the left side of the frame RightRunAroundSide, ///< Run other text around the right side of the frame EnoughRunAroundSide, ///< Run other text dynamically around both sides of the shape, provided there is sufficient space left BothRunAroundSide, ///< Run other text around both sides of the shape NoRunAround, ///< The text will be completely avoiding the frame by keeping the horizontal space that this frame occupies blank. RunThrough ///< The text will completely ignore the frame and layout as if it was not there }; /// The behavior text should do when intersecting this shape. enum TextRunAroundContour { ContourBox, /// Run other text around a bounding rect of the outline ContourFull, ///< Run other text around also on the inside ContourOutside ///< Run other text around only on the outside }; /** * TODO */ enum RunThroughLevel { Background, Foreground }; /** * @brief Constructor */ KoShape(); /** * @brief Destructor */ virtual ~KoShape(); /** * @brief creates a deep copy of thie shape or shapes subtree * @return a cloned shape */ virtual KoShape* cloneShape() const; /** * @brief Paint the shape * The class extending this one is responsible for painting itself. Since we do not * assume the shape is square the paint must also clear its background if it will draw * something transparent on top. * This can be done with a method like: * painter.fillRect(converter.normalToView(QRectF(QPointF(0.0,0.0), size())), background()); * Or equavalent for non-square objects. * Do note that a shape's top-left is always at coordinate 0,0. Even if the shape itself is rotated * or translated. * @param painter used for painting the shape * @param converter to convert between internal and view coordinates. * @see applyConversion() * @param paintcontext the painting context. */ virtual void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) = 0; /** * @brief Paint the shape's border * This is a helper function that could be called from the paint() method of all shapes. * @param painter used for painting the shape * @param converter to convert between internal and view coordinates. * @see applyConversion() */ virtual void paintBorder(QPainter &painter, const KoViewConverter &converter); /** * Load a shape from odf * * @param context the KoShapeLoadingContext used for loading * @param element element which represents the shape in odf * * @return false if loading failed */ virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) = 0; /** * @brief store the shape data as ODF XML. * This is the method that will be called when saving a shape as a described in * OpenDocument 9.2 Drawing Shapes. * @see saveOdfAttributes */ virtual void saveOdf(KoShapeSavingContext &context) const = 0; /** * This method can be used while saving the shape as ODF to add the data * stored on this shape to the current element. * * @param context the context for the current save. * @param attributes a number of OdfAttribute items to state which attributes to save. * @see saveOdf */ void saveOdfAttributes(KoShapeSavingContext &context, int attributes) const; /** * This method can be used while saving the shape as Odf to add common child elements * * The office:event-listeners and draw:glue-point are saved. * @param context the context for the current save. */ void saveOdfCommonChildElements(KoShapeSavingContext &context) const; /** * This method can be used to save contour data from the clipPath() * * The draw:contour-polygon or draw:contour-path elements are saved. * @param context the context for the current save. * @param originalSize the original size of the unscaled image. */ void saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const; /** * @brief Scale the shape using the zero-point which is the top-left corner. * @see position() * * @param sx scale in x direction * @param sy scale in y direction */ void scale(qreal sx, qreal sy); /** * @brief Rotate the shape (relative) * * The shape will be rotated from the current rotation using the center of the shape using the size() * * @param angle change the angle of rotation increasing it with 'angle' degrees */ void rotate(qreal angle); /** * Return the current rotation in degrees. * It returns NaN if the shape has a shearing or scaling transformation applied. */ qreal rotation() const; /** * @brief Shear the shape * The shape will be sheared using the zero-point which is the top-left corner. * @see position() * * @param sx shear in x direction * @param sy shear in y direction */ void shear(qreal sx, qreal sy); /** * @brief Resize the shape * * @param size the new size of the shape. This is different from scaling as * scaling is a so called secondary operation which is comparable to zooming in * instead of changing the size of the basic shape. * Easiest example of this difference is that using this method will not distort the * size of pattern-fills and strokes. */ virtual void setSize(const QSizeF &size); /** * @brief Get the size of the shape in pt. * * The size is in shape coordinates. * * @return the size of the shape as set by setSize() */ virtual QSizeF size() const; /** * @brief Set the position of the shape in pt * * @param position the new position of the shape */ virtual void setPosition(const QPointF &position); /** * @brief Get the position of the shape in pt * * @return the position of the shape */ QPointF position() const; /** * @brief Check if the shape is hit on position * @param position the position where the user clicked. * @return true when it hits. */ virtual bool hitTest(const QPointF &position) const; /** * @brief Get the bounding box of the shape * * This includes the line width and the shadow of the shape * * @return the bounding box of the shape */ virtual QRectF boundingRect() const; + /** + * Get the united bounding box of a group of shapes. This is a utility + * function used in many places in Krita. + */ + static QRectF boundingRect(const QList &shapes); + /** * @brief Add a connector point to the shape * * A connector is a place on the shape that allows a graphical connection to be made * using a line, for example. * * @param point the connection point to add * @return the id of the new connection point */ int addConnectionPoint(const KoConnectionPoint &point); /** * Sets data of connection point with specified id. * * The position of the connector is restricted to the bounding rectangle of the shape. * When setting a default connection point, the new position is ignored, as these * are fixed at their default position. * The function will insert a new connection point if the specified id was not used * before. * * @param connectionPointId the id of the connection point to set * @param point the connection point data * @return false if specified connection point id is invalid, else true */ bool setConnectionPoint(int connectionPointId, const KoConnectionPoint &point); /// Checks if a connection point with the specified id exists bool hasConnectionPoint(int connectionPointId) const; /// Returns connection point with specified connection point id KoConnectionPoint connectionPoint(int connectionPointId) const; /** * Return a list of the connection points that have been added to this shape. * All the points are relative to the shape position, see absolutePosition(). * @return a list of the connectors that have been added to this shape. */ KoConnectionPoints connectionPoints() const; /// Removes connection point with specified id void removeConnectionPoint(int connectionPointId); /// Removes all connection points void clearConnectionPoints(); /** * Return the side text should flow around this shape. This implements the ODF style:wrap * attribute that specifies how text is displayed around a frame or graphic object. */ TextRunAroundSide textRunAroundSide() const; /** * Set the side text should flow around this shape. * @param side the requested side * @param runThrought run through the foreground or background or... */ void setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrough = Background); /** * The space between this shape's left edge and text that runs around this shape. * @return the space around this shape to keep free from text */ qreal textRunAroundDistanceLeft() const; /** * Set the space between this shape's left edge and the text that run around this shape. * @param distance the space around this shape to keep free from text */ void setTextRunAroundDistanceLeft(qreal distance); /** * The space between this shape's top edge and text that runs around this shape. * @return the space around this shape to keep free from text */ qreal textRunAroundDistanceTop() const; /** * Set the space between this shape's top edge and the text that run around this shape. * @param distance the space around this shape to keep free from text */ void setTextRunAroundDistanceTop(qreal distance); /** * The space between this shape's right edge and text that runs around this shape. * @return the space around this shape to keep free from text */ qreal textRunAroundDistanceRight() const; /** * Set the space between this shape's right edge and the text that run around this shape. * @param distance the space around this shape to keep free from text */ void setTextRunAroundDistanceRight(qreal distance); /** * The space between this shape's bottom edge and text that runs around this shape. * @return the space around this shape to keep free from text */ qreal textRunAroundDistanceBottom() const; /** * Set the space between this shape's bottom edge and the text that run around this shape. * @param distance the space around this shape to keep free from text */ void setTextRunAroundDistanceBottom(qreal distance); /** * Return the threshold above which text should flow around this shape. * The text will not flow around the shape on a side unless the space available on that side * is above this threshold. Only used when the text run around side is EnoughRunAroundSide. * @return threshold the threshold */ qreal textRunAroundThreshold() const; /** * Set the threshold above which text should flow around this shape. * The text will not flow around the shape on a side unless the space available on that side * is above this threshold. Only used when the text run around side is EnoughRunAroundSide. * @param threshold the new threshold */ void setTextRunAroundThreshold(qreal threshold); /** * Return the how tight text run around is done around this shape. * @return the contour */ TextRunAroundContour textRunAroundContour() const; /** * Set how tight text run around is done around this shape. * @param contour the new contour */ void setTextRunAroundContour(TextRunAroundContour contour); /** * Set the KoShapeAnchor */ void setAnchor(KoShapeAnchor *anchor); /** * Return the KoShapeAnchor, or 0 */ KoShapeAnchor *anchor() const; /** * Set the minimum height of the shape. * Currently it's not respected but only for informational purpose * @param minimumShapeHeight the minimum height of the frame. */ void setMinimumHeight(qreal height); /** * Return the minimum height of the shape. * @return the minimum height of the shape. Default is 0.0. */ qreal minimumHeight() const; /** * Set the background of the shape. * A shape background can be a plain color, a gradient, a pattern, be fully transparent * or have a complex fill. * Setting such a background will allow the shape to be filled and will be able to tell * if it is transparent or not. * @param background the new shape background. */ void setBackground(QSharedPointer background); /** * return the brush used to paint te background of this shape with. * A QBrush can have a plain color, be fully transparent or have a complex fill. * setting such a brush will allow the shape to fill itself using that brush and * will be able to tell if its transparent or not. * @return the background-brush */ QSharedPointer background() const; /** * Returns true if there is some transparency, false if the shape is fully opaque. * The default implementation will just return if the background has some transparency, * you should override it and always return true if your shape is not square. * @return if the shape is (partly) transparent. */ virtual bool hasTransparency() const; /** * Sets shape level transparency. * @param transparency the new shape level transparency */ void setTransparency(qreal transparency); /** * Returns the shape level transparency. * @param recursive when true takes the parents transparency into account */ qreal transparency(bool recursive=false) const; /** * Retrieve the z-coordinate of this shape. * The zIndex property is used to determine which shape lies on top of other objects. * An shape with a higher z-order is on top, and can obscure another shape. * @return the z-index of this shape. * @see setZIndex() */ int zIndex() const; /** * Set the z-coordinate of this shape. * The zIndex property is used to determine which shape lies on top of other objects. * An shape with a higher z-order is on top, and can obscure, another shape. *

Just like two objects having the same x or y coordinate will make them 'touch', * so will two objects with the same z-index touch on the z plane. In layering the * shape this, however, can cause a little confusion as one always has to be on top. * The layering if two overlapping objects have the same index is implementation dependent * and probably depends on the order in which they are added to the shape manager. * @param zIndex the new z-index; */ void setZIndex(int zIndex); /** * Retrieve the run through property of this shape. * The run through property is used to determine if the shape is behind, inside or before text. * @return the run through of this shape. */ int runThrough(); /** * Set the run through property of this shape. * The run through property is used to determine if the shape is behind, inside or before text. * @param runThrough the new run through; */ virtual void setRunThrough(short int runThrough); /** * Changes the Shape to be visible or invisible. * Being visible means being painted, as well as being used for * things like guidelines or searches. * @param on when true; set the shape to be visible. * @see setGeometryProtected(), setContentProtected(), setSelectable() */ void setVisible(bool on); /** * Returns current visibility state of this shape. * Being visible means being painted, as well as being used for * things like guidelines or searches. * @param recursive when true, checks visibility recursively * @return current visibility state of this shape. * @see isGeometryProtected(), isContentProtected(), isSelectable() */ bool isVisible(bool recursive = false) const; /** * Changes the shape to be printable or not. The default is true. * * If a Shape's print flag is true, the shape will be printed. If * false, the shape will not be printed. If a shape is not visible (@see isVisible), * it isPrinted will return false, too. */ void setPrintable(bool on); /** * Returns the current printable state of this shape. * * A shape can be visible but not printable, not printable and not visible * or visible and printable, but not invisible and still printable. * * @return current printable state of this shape. */ bool isPrintable() const; /** * Makes it possible for the user to select this shape. * This parameter defaults to true. * @param selectable when true; set the shape to be selectable by the user. * @see setGeometryProtected(), setContentProtected(), setVisible() */ void setSelectable(bool selectable); /** * Returns if this shape can be selected by the user. * @return true only when the object is selectable. * @see isGeometryProtected(), isContentProtected(), isVisible() */ bool isSelectable() const; /** * Tells the shape to have its position/rotation and size protected from user-changes. * The geometry being protected means the user can not change shape or position of the * shape. This includes any matrix operation such as rotation. * @param on when true; set the shape to have its geometry protected. * @see setContentProtected(), setSelectable(), setVisible() */ void setGeometryProtected(bool on); /** * Returns current geometry protection state of this shape. * The geometry being protected means the user can not change shape or position of the * shape. This includes any matrix operation such as rotation. * @return current geometry protection state of this shape. * @see isContentProtected(), isSelectable(), isVisible() */ bool isGeometryProtected() const; /** * Marks the shape to have its content protected against editing. * Content protection is a hint for tools to disallow the user editing the content. * @param protect when true set the shapes content to be protected from user modification. * @see setGeometryProtected(), setSelectable(), setVisible() */ void setContentProtected(bool protect); /** * Returns current content protection state of this shape. * Content protection is a hint for tools to disallow the user editing the content. * @return current content protection state of this shape. * @see isGeometryProtected(), isSelectable(), isVisible() */ bool isContentProtected() const; /** * Returns the parent, or 0 if there is no parent. * @return the parent, or 0 if there is no parent. */ KoShapeContainer *parent() const; /** * Set the parent of this shape. * @param parent the new parent of this shape. Can be 0 if the shape has no parent anymore. */ void setParent(KoShapeContainer *parent); /** * Request a repaint to be queued. * The repaint will be of the entire Shape, including its selection handles should this * shape be selected. *

This method will return immediately and only request a repaint. Successive calls * will be merged into an appropriate repaint action. */ virtual void update() const; /** * Request a repaint to be queued. * The repaint will be restricted to the parameters rectangle, which is expected to be * in points (the internal coordinates system of KoShape) and it is expected to be * normalized. *

This method will return immediately and only request a repaint. Successive calls * will be merged into an appropriate repaint action. * @param rect the rectangle (in pt) to queue for repaint. */ virtual void update(const QRectF &rect) const; /// Used by compareShapeZIndex() to order shapes enum ChildZOrderPolicy { ChildZDefault, ChildZParentChild = ChildZDefault, ///< normal parent/child ordering ChildZPassThrough ///< children are considered equal to this shape }; /** * Returns if during compareShapeZIndex() how this shape portrays the values * of its children. The default behaviour is to let this shape's z values take * the place of its childrens values, so you get a parent/child relationship. * The children are naturally still ordered relatively to their z values * * But for special cases (like Calligra's TextShape) it can be overloaded to return * ChildZPassThrough which means the children keep their own z values * @returns the z order policy of this shape */ virtual ChildZOrderPolicy childZOrderPolicy(); /** * This is a method used to sort a list using the STL sorting methods. * @param s1 the first shape * @param s2 the second shape */ static bool compareShapeZIndex(KoShape *s1, KoShape *s2); /** * returns the outline of the shape in the form of a path. * The outline returned will always be relative to the position() of the shape, so * moving the shape will not alter the result. The outline is used to draw the stroke * on, for example. * @returns the outline of the shape in the form of a path. */ virtual QPainterPath outline() const; /** * returns the outline of the shape in the form of a rect. * The outlineRect returned will always be relative to the position() of the shape, so * moving the shape will not alter the result. The outline is used to calculate * the boundingRect. * @returns the outline of the shape in the form of a rect. */ virtual QRectF outlineRect() const; /** * returns the outline of the shape in the form of a path for the use of painting a shadow. * * Normally this would be the same as outline() if there is a fill (background) set on the * shape and empty if not. However, a shape could reimplement this to return an outline * even if no fill is defined. A typical example of this would be the picture shape * which has a picture but almost never a background. * * @returns the outline of the shape in the form of a path. */ virtual QPainterPath shadowOutline() const; /** * Returns the currently set stroke, or 0 if there is no stroke. * @return the currently set stroke, or 0 if there is no stroke. */ KoShapeStrokeModelSP stroke() const; /** * Set a new stroke, removing the old one. * @param stroke the new stroke, or 0 if there should be no stroke. */ void setStroke(KoShapeStrokeModelSP stroke); /** * Return the insets of the stroke. * Convenience method for KoShapeStrokeModel::strokeInsets() */ KoInsets strokeInsets() const; /// Sets the new shadow, removing the old one void setShadow(KoShapeShadow *shadow); /// Returns the currently set shadow or 0 if there is no shadow set KoShapeShadow *shadow() const; /// Sets the new border, removing the old one. void setBorder(KoBorder *border); /// Returns the currently set border or 0 if there is no border set KoBorder *border() const; /// Sets a new clip path, removing the old one void setClipPath(KoClipPath *clipPath); /// Returns the currently set clip path or 0 if there is no clip path set KoClipPath * clipPath() const; /// Sets a new clip mask, removing the old one. The mask is owned by the shape. void setClipMask(KoClipMask *clipMask); /// Returns the currently set clip mask or 0 if there is no clip mask set KoClipMask* clipMask() const; /** * Setting the shape to keep its aspect-ratio has the effect that user-scaling will * keep the width/hight ratio intact so as not to distort shapes that rely on that * ratio. * @param keepAspect the new value */ void setKeepAspectRatio(bool keepAspect); /** * Setting the shape to keep its aspect-ratio has the effect that user-scaling will * keep the width/hight ratio intact so as not to distort shapes that rely on that * ratio. * @return whether to keep aspect ratio of this shape */ bool keepAspectRatio() const; /** * Return the position of this shape regardless of rotation/skew/scaling and regardless of * this shape having a parent (being in a group) or not.
* @param anchor The place on the (unaltered) shape that you want the position of. * @return the point that is the absolute, centered position of this shape. */ QPointF absolutePosition(KoFlake::AnchorPosition anchor = KoFlake::Center) const; /** * Move this shape to an absolute position where the end location will be the same * regardless of the shape's rotation/skew/scaling and regardless of this shape having * a parent (being in a group) or not.
* The newPosition is going to be the center of the shape. * This has the convenient effect that:

     shape->setAbsolutePosition(QPointF(0,0));
     shape->rotate(45);
Will result in the same visual position of the shape as the opposite:
     shape->rotate(45);
     shape->setAbsolutePosition(QPointF(0,0));
* @param newPosition the new absolute center of the shape. * @param anchor The place on the (unaltered) shape that you set the position of. */ void setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor = KoFlake::Center); /** * Set a data object on the shape to be used by an application. * This is specifically useful when a shape is created in a plugin and that data from that * shape should be accessible outside the plugin. * @param userData the new user data, or 0 to delete the current one. */ void setUserData(KoShapeUserData *userData); /** * Return the current userData. */ KoShapeUserData *userData() const; /** * Return the Id of this shape, identifying the type of shape by the id of the factory. * @see KoShapeFactoryBase::shapeId() * @return the id of the shape-type */ QString shapeId() const; /** * Set the Id of this shape. A shapeFactory is expected to set the Id at creation * so applications can find out what kind of shape this is. * @see KoShapeFactoryBase::shapeId() * @param id the ID from the factory that created this shape */ void setShapeId(const QString &id); /** * Create a matrix that describes all the transformations done on this shape. * * The absolute transformation is the combined transformation of this shape * and all its parents and grandparents. * * @param converter if not null, this method uses the converter to mark the right * offsets in the current view. */ QTransform absoluteTransformation(const KoViewConverter *converter) const; /** * Applies a transformation to this shape. * * The transformation given is relative to the global coordinate system, i.e. the document. * This is a convenience function to apply a global transformation to this shape. * @see applyTransformation * * @param matrix the transformation matrix to apply */ void applyAbsoluteTransformation(const QTransform &matrix); /** * Sets a new transformation matrix describing the local transformations on this shape. * @param matrix the new transformation matrix */ void setTransformation(const QTransform &matrix); /// Returns the shapes local transformation matrix QTransform transformation() const; /** * Applies a transformation to this shape. * * The transformation given is relative to the shape coordinate system. * * @param matrix the transformation matrix to apply */ void applyTransformation(const QTransform &matrix); /** * Copy all the settings from the parameter shape and apply them to this shape. * Settings like the position and rotation to visible and locked. The parent * is a notable exclusion. * @param shape the shape to use as original */ void copySettings(const KoShape *shape); /** * Convenience method that allows people implementing paint() to use the shape * internal coordinate system directly to paint itself instead of considering the * views zoom. * @param painter the painter to alter the zoom level of. * @param converter the converter for the current views zoom. */ static void applyConversion(QPainter &painter, const KoViewConverter &converter); /** * A convenience method that creates a handles helper with applying transformations at * the same time. Please note that you shouldn't save/restore additionally. All the work * on restoring original painter's transformations is done by the helper. */ static KisHandlePainterHelper createHandlePainterHelper(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius = 0.0); /** * @brief Transforms point from shape coordinates to document coordinates * @param point in shape coordinates * @return point in document coordinates */ QPointF shapeToDocument(const QPointF &point) const; /** * @brief Transforms rect from shape coordinates to document coordinates * @param rect in shape coordinates * @return rect in document coordinates */ QRectF shapeToDocument(const QRectF &rect) const; /** * @brief Transforms point from document coordinates to shape coordinates * @param point in document coordinates * @return point in shape coordinates */ QPointF documentToShape(const QPointF &point) const; /** * @brief Transform rect from document coordinates to shape coordinates * @param rect in document coordinates * @return rect in shape coordinates */ QRectF documentToShape(const QRectF &rect) const; /** * Returns the name of the shape. * @return the shapes name */ QString name() const; /** * Sets the name of the shape. * @param name the new shape name */ void setName(const QString &name); /** * Update the position of the shape in the tree of the KoShapeManager. */ void notifyChanged(); /** * A shape can be in a state that it is doing processing data like loading or text layout. * In this case it can be shown on screen probably partially but it should really not be printed * until it is fully done processing. * Warning! This method can be blocking for a long time * @param asynchronous If set to true the processing will can take place in a different thread and the * function will not block until the shape is finised. * In case of printing Flake will call this method from a non-main thread and only * start printing it when the in case of printing method returned. * If set to false the processing needs to be done synchronously and will * block until the result is finished. */ virtual void waitUntilReady(const KoViewConverter &converter, bool asynchronous = true) const; /// checks recursively if the shape or one of its parents is not visible or locked bool isEditable() const; /** * Adds a shape which depends on this shape. * Making a shape dependent on this one means it will get shapeChanged() called * on each update of this shape. * * If this shape already depends on the given shape, establishing the * dependency is refused to prevent circular dependencies. * * @param shape the shape which depends on this shape * @return true if dependency could be established, otherwise false * @see removeDependee(), hasDependee() */ bool addDependee(KoShape *shape); /** * Removes as shape depending on this shape. * @see addDependee(), hasDependee() */ void removeDependee(KoShape *shape); /// Returns if the given shape is dependent on this shape bool hasDependee(KoShape *shape) const; /// Returns list of shapes depending on this shape QList dependees() const; /// Returns additional snap data the shape wants to have snapping to virtual KoSnapData snapData() const; /** * Set additional attribute * * This can be used to attach additional attributes to a shape for attributes * that are application specific like presentation:placeholder * * @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder * @param value The value of the attribute */ void setAdditionalAttribute(const QString &name, const QString &value); /** * Remove additional attribute * * @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder */ void removeAdditionalAttribute(const QString &name); /** * Check if additional attribute is set * * @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder * * @return true if there is a attribute with prefix:tag set, false otherwise */ bool hasAdditionalAttribute(const QString &name) const; /** * Get additional attribute * * @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder * * @return The value of the attribute if it exists or a null string if not found. */ QString additionalAttribute(const QString &name) const; void setAdditionalStyleAttribute(const char *name, const QString &value); void removeAdditionalStyleAttribute(const char *name); /** * Returns the filter effect stack of the shape * * @return the list of filter effects applied on the shape when rendering. */ KoFilterEffectStack *filterEffectStack() const; /// Sets the new filter effect stack, removing the old one void setFilterEffectStack(KoFilterEffectStack *filterEffectStack); /** * Set the property collision detection. * Setting this to true will result in calls to shapeChanged() with the CollisionDetected * parameter whenever either this or another shape is moved/rotated etc and intersects this shape. * @param detect if true detect collisions. */ void setCollisionDetection(bool detect); /** * get the property collision detection. * @returns true if collision detection is on. */ bool collisionDetection(); /** * Return the tool delegates for this shape. * In Flake a shape being selected will cause the tool manager to make available all tools that * can edit the selected shapes. In some cases selecting one shape should allow the tool to * edit a related shape be available too. The tool delegates allows this to happen by taking * all the shapes in the set into account on tool selection. * Notice that if the set is non-empty 'this' shape is no longer looked at. You can choose * to add itself to the set too. */ QSet toolDelegates() const; /** * Set the tool delegates. * @param delegates the new delegates. * @see toolDelegates() */ void setToolDelegates(const QSet &delegates); /** * Return the hyperlink for this shape. */ QString hyperLink () const; /** * Set hyperlink for this shape. * @param hyperLink name. */ void setHyperLink(const QString &hyperLink); /** * \internal * Returns the private object for use within the flake lib */ KoShapePrivate *priv(); public: struct ShapeChangeListener { virtual ~ShapeChangeListener(); virtual void notifyShapeChanged(ChangeType type, KoShape *shape) = 0; private: friend class KoShape; friend class KoShapePrivate; void registerShape(KoShape *shape); void unregisterShape(KoShape *shape); void notifyShapeChangedImpl(ChangeType type, KoShape *shape); QList m_registeredShapes; }; void addShapeChangeListener(ShapeChangeListener *listener); void removeShapeChangeListener(ShapeChangeListener *listener); public: static QList linearizeSubtree(const QList &shapes); protected: /// constructor KoShape(KoShapePrivate *); /* ** loading saving helper methods */ /// attributes from ODF 1.1 chapter 9.2.15 Common Drawing Shape Attributes enum OdfAttribute { OdfTransformation = 1, ///< Store transformation information OdfSize = 2, ///< Store size information OdfPosition = 8, ///< Store position OdfAdditionalAttributes = 4, ///< Store additional attributes of the shape OdfCommonChildElements = 16, ///< Event actions and connection points OdfLayer = 64, ///< Store layer name OdfStyle = 128, ///< Store the style OdfId = 256, ///< Store the unique ID OdfName = 512, ///< Store the name of the shape OdfZIndex = 1024, ///< Store the z-index OdfViewbox = 2048, ///< Store the viewbox /// A mask for all mandatory attributes OdfMandatories = OdfLayer | OdfStyle | OdfId | OdfName | OdfZIndex, /// A mask for geometry attributes OdfGeometry = OdfPosition | OdfSize, /// A mask for all the attributes OdfAllAttributes = OdfTransformation | OdfGeometry | OdfAdditionalAttributes | OdfMandatories | OdfCommonChildElements }; /** * This method is used during loading of the shape to load common attributes * * @param context the KoShapeLoadingContext used for loading * @param element element which represents the shape in odf * @param attributes a number of OdfAttribute items to state which attributes to load. */ bool loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes); /** * Parses the transformation attribute from the given string * @param transform the transform attribute string * @return the resulting transformation matrix */ QTransform parseOdfTransform(const QString &transform); /** * @brief Saves the style used for the shape * * This method fills the given style object with the stroke and * background properties and then adds the style to the context. * * @param style the style object to fill * @param context used for saving * @return the name of the style * @see saveOdf */ virtual QString saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const; /** * Loads the stroke and fill style from the given element. * * @param element the xml element to load the style from * @param context the loading context used for loading */ virtual void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context); /// Loads the stroke style KoShapeStrokeModelSP loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const; /// Loads the fill style QSharedPointer loadOdfFill(KoShapeLoadingContext &context) const; /// Loads the connection points void loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context); /// Loads the clip contour void loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor); /* ** end loading saving */ /** * A hook that allows inheriting classes to do something after a KoShape property changed * This is called whenever the shape, position rotation or scale properties were altered. * @param type an indicator which type was changed. */ virtual void shapeChanged(ChangeType type, KoShape *shape = 0); /// return the current matrix that contains the rotation/scale/position of this shape QTransform transform() const; KoShapePrivate *d_ptr; private: Q_DECLARE_PRIVATE(KoShape) }; Q_DECLARE_METATYPE(KoShape*) #endif diff --git a/libs/flake/KoShapeBasedDocumentBase.cpp b/libs/flake/KoShapeBasedDocumentBase.cpp index fc13c9e9c0..b8dfc0725e 100644 --- a/libs/flake/KoShapeBasedDocumentBase.cpp +++ b/libs/flake/KoShapeBasedDocumentBase.cpp @@ -1,82 +1,89 @@ /* This file is part of the KDE project Copyright (C) 2006, 2010 Thomas Zander Copyright (C) 2011 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ +#include #include "KoShapeBasedDocumentBase.h" #include "KoDocumentResourceManager.h" #include "KoShapeRegistry.h" #include "KoShapeFactoryBase.h" #include #include #include class KoShapeBasedDocumentBasePrivate { public: KoShapeBasedDocumentBasePrivate() : resourceManager(new KoDocumentResourceManager()) { KoShapeRegistry *registry = KoShapeRegistry::instance(); foreach (const QString &id, registry->keys()) { KoShapeFactoryBase *shapeFactory = registry->value(id); shapeFactory->newDocumentResourceManager(resourceManager); } // read persistent application wide resources KSharedConfigPtr config = KSharedConfig::openConfig(); - if (config->hasGroup("Misc")) { - KConfigGroup miscGroup = config->group("Misc"); - const qreal pasteOffset = miscGroup.readEntry("CopyOffset", 10.0); - resourceManager->setPasteOffset(pasteOffset); - const bool pasteAtCursor = miscGroup.readEntry("PasteAtCursor", true); - resourceManager->enablePasteAtCursor(pasteAtCursor); - const uint grabSensitivity = miscGroup.readEntry("GrabSensitivity", 3); - resourceManager->setGrabSensitivity(grabSensitivity); - const uint handleRadius = miscGroup.readEntry("HandleRadius", 3); - resourceManager->setHandleRadius(handleRadius); - } + KConfigGroup miscGroup = config->group("Misc"); + const qreal pasteOffset = miscGroup.readEntry("CopyOffset", 10.0); + resourceManager->setPasteOffset(pasteOffset); + const bool pasteAtCursor = miscGroup.readEntry("PasteAtCursor", true); + resourceManager->enablePasteAtCursor(pasteAtCursor); + const uint grabSensitivity = miscGroup.readEntry("GrabSensitivity", 3); + resourceManager->setGrabSensitivity(grabSensitivity); + const uint handleRadius = miscGroup.readEntry("HandleRadius", 3); + resourceManager->setHandleRadius(handleRadius); } ~KoShapeBasedDocumentBasePrivate() { delete resourceManager; } KoDocumentResourceManager *resourceManager; }; KoShapeBasedDocumentBase::KoShapeBasedDocumentBase() : d(new KoShapeBasedDocumentBasePrivate()) { } KoShapeBasedDocumentBase::~KoShapeBasedDocumentBase() { delete d; } void KoShapeBasedDocumentBase::shapesRemoved(const QList & /*shapes*/, KUndo2Command * /*command*/) { } KoDocumentResourceManager *KoShapeBasedDocumentBase::resourceManager() const { return d->resourceManager; } + +QRectF KoShapeBasedDocumentBase::documentRect() const +{ + const qreal pxToPt = 72.0 / pixelsPerInch(); + + QTransform t = QTransform::fromScale(pxToPt, pxToPt); + return t.mapRect(documentRectInPixels()); +} diff --git a/libs/flake/KoShapeBasedDocumentBase.h b/libs/flake/KoShapeBasedDocumentBase.h index b2f3565b03..95e6327d6d 100644 --- a/libs/flake/KoShapeBasedDocumentBase.h +++ b/libs/flake/KoShapeBasedDocumentBase.h @@ -1,85 +1,102 @@ /* This file is part of the KDE project Copyright (C) 2006 Jan Hambrecht Copyright (C) 2006, 2010 Thomas Zander Copyright (C) 2008 C. Boemann 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 KOSHAPEBASEDDOCUMENTBASE_H #define KOSHAPEBASEDDOCUMENTBASE_H #include "kritaflake_export.h" #include +class QRectF; class KoShape; class KoShapeBasedDocumentBasePrivate; class KoDocumentResourceManager; class KUndo2Command; /** * The KoShapeBasedDocumentBase is an abstract interface that the application's class * that owns the shapes should implement. This tends to be the document. * @see KoShapeDeleteCommand, KoShapeCreateCommand */ class KRITAFLAKE_EXPORT KoShapeBasedDocumentBase { public: KoShapeBasedDocumentBase(); virtual ~KoShapeBasedDocumentBase(); /** * Add a shape to the shape controller, allowing it to be seen and saved. * The controller should add the shape to the ShapeManager instance(s) manually * if the shape is one that should be currently shown on screen. * @param shape the new shape */ virtual void addShape(KoShape *shape) = 0; /** * Remove a shape from the shape controllers control, allowing it to be deleted shortly after * The controller should remove the shape from all the ShapeManager instance(s) manually * @param shape the shape to remove */ virtual void removeShape(KoShape *shape) = 0; /** * This method gets called after the KoShapeDeleteCommand is executed * * This passes the KoShapeDeleteCommand as the command parameter. This makes it possible * for applications that need to do something after the KoShapeDeleteCommand is done, e.g. * adding one commands that need to be executed when a shape was deleted. * The default implementation is empty. * @param shapes The list of shapes that got removed. * @param command The command that was used to remove the shapes from the document. */ virtual void shapesRemoved(const QList &shapes, KUndo2Command *command); /** * Return a pointer to the resource manager associated with the * shape-set (typically a document). The resource manager contains * document wide resources * such as variable managers, the image * collection and others. */ virtual KoDocumentResourceManager *resourceManager() const; + /** + * The size of the document measured in rasterized pixels. This information is needed for loading + * SVG documents that use 'px' as the default unit. + */ + virtual QRectF documentRectInPixels() const = 0; + + /** + * The size of the document measured in 'pt' + */ + QRectF documentRect() const; + + /** + * Resolution of the rasterized representaiton of the document. Used to load SVG documents correctly. + */ + virtual qreal pixelsPerInch() const = 0; + private: KoShapeBasedDocumentBasePrivate * const d; }; #endif diff --git a/libs/flake/KoShapeController.cpp b/libs/flake/KoShapeController.cpp index 7d4559247a..d2f692fd34 100644 --- a/libs/flake/KoShapeController.cpp +++ b/libs/flake/KoShapeController.cpp @@ -1,185 +1,200 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2006-2008 Thorsten Zachmann * Copyright (C) 2011 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeController.h" #include "KoShapeBasedDocumentBase.h" #include "KoShapeRegistry.h" #include "KoDocumentResourceManager.h" #include "KoShapeManager.h" #include "KoShapeLayer.h" #include "KoSelection.h" #include "commands/KoShapeCreateCommand.h" #include "commands/KoShapeDeleteCommand.h" #include "commands/KoShapeConnectionChangeCommand.h" #include "KoCanvasBase.h" #include "KoShapeConfigWidgetBase.h" #include "KoShapeFactoryBase.h" #include "KoShape.h" #include "KoConnectionShape.h" #include #include #include #include class KoShapeController::Private { public: Private() : canvas(0), shapeBasedDocument(0) { } KoCanvasBase *canvas; KoShapeBasedDocumentBase *shapeBasedDocument; KUndo2Command* addShape(KoShape *shape, bool showDialog, KUndo2Command *parent) { if (canvas) { if (showDialog) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shape->shapeId()); Q_ASSERT(factory); int z = 0; Q_FOREACH (KoShape *sh, canvas->shapeManager()->shapes()) z = qMax(z, sh->zIndex()); shape->setZIndex(z + 1); // show config dialog. KPageDialog *dialog = new KPageDialog(canvas->canvasWidget()); dialog->setWindowTitle(i18n("%1 Options", factory->name())); int pageCount = 0; QList widgets; Q_FOREACH (KoShapeConfigWidgetBase* panel, factory->createShapeOptionPanels()) { if (! panel->showOnShapeCreate()) continue; panel->open(shape); panel->connect(panel, SIGNAL(accept()), dialog, SLOT(accept())); widgets.append(panel); panel->setResourceManager(canvas->resourceManager()); panel->setUnit(canvas->unit()); QString title = panel->windowTitle().isEmpty() ? panel->objectName() : panel->windowTitle(); dialog->addPage(panel, title); pageCount ++; } if (pageCount > 0) { if (pageCount > 1) dialog->setFaceType(KPageDialog::Tabbed); if (dialog->exec() != KPageDialog::Accepted) { delete dialog; return 0; } Q_FOREACH (KoShapeConfigWidgetBase *widget, widgets) widget->save(); } delete dialog; } // set the active layer as parent if there is not yet a parent. if (!shape->parent()) { shape->setParent(canvas->shapeManager()->selection()->activeLayer()); } } return new KoShapeCreateCommand(shapeBasedDocument, shape, parent); } void handleAttachedConnections(KoShape *shape, KUndo2Command *parentCmd) { foreach (KoShape *dependee, shape->dependees()) { KoConnectionShape *connection = dynamic_cast(dependee); if (connection) { if (shape == connection->firstShape()) { new KoShapeConnectionChangeCommand(connection, KoConnectionShape::StartHandle, shape, connection->firstConnectionId(), 0, -1, parentCmd); } else if (shape == connection->secondShape()) { new KoShapeConnectionChangeCommand(connection, KoConnectionShape::EndHandle, shape, connection->secondConnectionId(), 0, -1, parentCmd); } } } } }; KoShapeController::KoShapeController(KoCanvasBase *canvas, KoShapeBasedDocumentBase *shapeBasedDocument) : d(new Private()) { d->canvas = canvas; d->shapeBasedDocument = shapeBasedDocument; if (shapeBasedDocument) { shapeBasedDocument->resourceManager()->setShapeController(this); } } KoShapeController::~KoShapeController() { delete d; } KUndo2Command* KoShapeController::addShape(KoShape *shape, KUndo2Command *parent) { return d->addShape(shape, true, parent); } KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KUndo2Command *parent) { return d->addShape(shape, false, parent); } KUndo2Command* KoShapeController::removeShape(KoShape *shape, KUndo2Command *parent) { KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeBasedDocument, shape, parent); QList shapes; shapes.append(shape); d->shapeBasedDocument->shapesRemoved(shapes, cmd); // detach shape from any attached connection shapes d->handleAttachedConnections(shape, cmd); return cmd; } KUndo2Command* KoShapeController::removeShapes(const QList &shapes, KUndo2Command *parent) { KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeBasedDocument, shapes, parent); d->shapeBasedDocument->shapesRemoved(shapes, cmd); foreach (KoShape *shape, shapes) { d->handleAttachedConnections(shape, cmd); } return cmd; } void KoShapeController::setShapeControllerBase(KoShapeBasedDocumentBase *shapeBasedDocument) { d->shapeBasedDocument = shapeBasedDocument; } +QRectF KoShapeController::documentRectInPixels() const +{ + return d->shapeBasedDocument ? d->shapeBasedDocument->documentRectInPixels() : QRectF(0,0,1920,1080); +} + +qreal KoShapeController::pixelsPerInch() const +{ + return d->shapeBasedDocument ? d->shapeBasedDocument->pixelsPerInch() : 72.0; +} + +QRectF KoShapeController::documentRect() const +{ + return d->shapeBasedDocument ? d->shapeBasedDocument->documentRect() : documentRectInPixels(); +} + KoDocumentResourceManager *KoShapeController::resourceManager() const { if (!d->shapeBasedDocument) return 0; return d->shapeBasedDocument->resourceManager(); } KoShapeBasedDocumentBase *KoShapeController::documentBase() const { return d->shapeBasedDocument; } diff --git a/libs/flake/KoShapeController.h b/libs/flake/KoShapeController.h index 42f7d45068..377cae0dbe 100644 --- a/libs/flake/KoShapeController.h +++ b/libs/flake/KoShapeController.h @@ -1,135 +1,151 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2006-2008 Thorsten Zachmann * * 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 KOSHAPECONTROLLER_H #define KOSHAPECONTROLLER_H #include "kritaflake_export.h" #include #include class KoCanvasBase; class KoShape; class KoShapeBasedDocumentBase; class KUndo2Command; class KoDocumentResourceManager; /** * Class used by tools to maintain the list of shapes. * All applications have some sort of list of all shapes that belong to the document. * The applications implement the KoShapeBasedDocumentBase interface (all pure virtuals) * to add and remove shapes from the document. To ensure that an application can expect * a certain protocol to be adhered to when adding/removing shapes, all tools use the API * from this class for maintaining the list of shapes in the document. So no tool gets * to access the application directly. */ class KRITAFLAKE_EXPORT KoShapeController { public: /** * Create a new Controller; typically not called by applications, only * by the KonCanvasBase constructor. * @param canvas the canvas this controller works for. The canvas can be 0 * @param shapeBasedDocument the application provided shapeBasedDocument that we can call. */ KoShapeController(KoCanvasBase *canvas, KoShapeBasedDocumentBase *shapeBasedDocument); /// destructor ~KoShapeController(); /** * @brief Add a shape to the document. * If the shape has no parent, the active layer will become its parent. * * @param shape to add to the document * @param parent the parent command if the resulting command is a compound undo command. * * @return command which will insert the shape into the document or 0 if the * insertion was cancelled. The command is not yet executed. */ KUndo2Command* addShape(KoShape *shape, KUndo2Command *parent = 0); /** * @brief Add a shape to the document, skipping any dialogs or other user interaction. * * @param shape to add to the document * @param parent the parent command if the resulting command is a compound undo command. * * @return command which will insert the shape into the document. The command is not yet executed. */ KUndo2Command* addShapeDirect(KoShape *shape, KUndo2Command *parent = 0); /** * @brief Remove a shape from the document. * * @param shape to remove from the document * @param parent the parent command if the resulting command is a compound undo command. * * @return command which will remove the shape from the document. * The command is not yet executed. */ KUndo2Command* removeShape(KoShape *shape, KUndo2Command *parent = 0); /** * Remove a shape from the document. * * @param shapes the set of shapes to remove from the document * @param parent the parent command if the resulting command is a compound undo command. * * @return command which will remove the shape from the document. * The command is not yet executed. */ KUndo2Command* removeShapes(const QList &shapes, KUndo2Command *parent = 0); /** * @brief Set the KoShapeBasedDocumentBase used to add/remove shapes. * * NOTE: only Sheets uses this method. Do not use it in your application. Sheets * has to also call: * KoToolManager::instance()->updateShapeControllerBase(shapeBasedDocument, canvas->canvasController()); * * @param shapeBasedDocument the new shapeBasedDocument. */ void setShapeControllerBase(KoShapeBasedDocumentBase *shapeBasedDocument); + /** + * The size of the document measured in rasterized pixels. This information is needed for loading + * SVG documents that use 'px' as the default unit. + */ + QRectF documentRectInPixels() const; + + /** + * Resolution of the rasterized representaiton of the document. Used to load SVG documents correctly. + */ + qreal pixelsPerInch() const; + + /** + * Document rect measured in 'pt' + */ + QRectF documentRect() const; + /** * Return a pointer to the resource manager associated with the * shape-set (typically a document). The resource manager contains * document wide resources * such as variable managers, the image * collection and others. */ KoDocumentResourceManager *resourceManager() const; /** * @brief Returns the KoShapeBasedDocumentBase used to add/remove shapes. * * @return the KoShapeBasedDocumentBase */ KoShapeBasedDocumentBase *documentBase() const; private: class Private; Private * const d; }; Q_DECLARE_METATYPE(KoShapeController *) #endif diff --git a/libs/flake/KoSvgPaste.cpp b/libs/flake/KoSvgPaste.cpp new file mode 100644 index 0000000000..205cca0285 --- /dev/null +++ b/libs/flake/KoSvgPaste.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2017 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 "KoSvgPaste.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +KoSvgPaste::KoSvgPaste() +{ +} + +bool KoSvgPaste::hasShapes() const +{ + const QMimeData *mimeData = QApplication::clipboard()->mimeData(); + return mimeData && mimeData->hasFormat("image/svg+xml"); +} + +QList KoSvgPaste::fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize) const +{ + QList shapes; + + const QMimeData *mimeData = QApplication::clipboard()->mimeData(); + if (!mimeData) return shapes; + + QByteArray data = mimeData->data("image/svg+xml"); + if (data.isEmpty()) return shapes; + + KoXmlDocument doc; + + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + const bool documentValid = doc.setContent(data, false, &errorMsg, &errorLine, &errorColumn); + + if (!documentValid) { + errorFlake << "Failed to process an SVG file at" + << errorLine << ":" << errorColumn << "->" << errorMsg; + return shapes; + } + + KoDocumentResourceManager resourceManager; + SvgParser parser(&resourceManager); + parser.setResolution(viewportInPx, resolutionPPI); + + shapes = parser.parseSvg(doc.documentElement(), fragmentSize); + + return shapes; +} diff --git a/libs/flake/KoSvgPaste.h b/libs/flake/KoSvgPaste.h new file mode 100644 index 0000000000..98400cf6e1 --- /dev/null +++ b/libs/flake/KoSvgPaste.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 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 KOSVGPASTE_H +#define KOSVGPASTE_H + +#include "kritaflake_export.h" +#include + +class KoShape; +class QRectF; +class QSizeF; + +class KRITAFLAKE_EXPORT KoSvgPaste +{ +public: + KoSvgPaste(); + + bool hasShapes() const; + QList fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize = 0) const; +}; + +#endif // KOSVGPASTE_H diff --git a/libs/flake/KoToolManager.cpp b/libs/flake/KoToolManager.cpp index 7fc1ca04a2..b2571512a8 100644 --- a/libs/flake/KoToolManager.cpp +++ b/libs/flake/KoToolManager.cpp @@ -1,1053 +1,1059 @@ /* This file is part of the KDE project * * Copyright (c) 2005-2010 Boudewijn Rempt * Copyright (C) 2006-2008 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // flake #include "KoToolManager.h" #include "KoToolManager_p.h" #include "KoToolRegistry.h" #include "KoToolProxy.h" #include "KoToolProxy_p.h" #include "KoSelection.h" #include "KoCanvasController.h" #include "KoCanvasControllerWidget.h" #include "KoShape.h" #include "KoShapeLayer.h" #include "KoShapeRegistry.h" #include "KoShapeManager.h" #include "KoSelectedShapesProxy.h" #include "KoCanvasBase.h" #include "KoInputDeviceHandlerRegistry.h" #include "KoInputDeviceHandlerEvent.h" #include "KoPointerEvent.h" #include "tools/KoCreateShapesTool.h" #include "tools/KoZoomTool.h" #include "tools/KoPanTool.h" #include "kis_action_registry.h" #include "KoToolFactoryBase.h" +#include + // Qt + kde #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_GLOBAL_STATIC(KoToolManager, s_instance) class CanvasData { public: CanvasData(KoCanvasController *cc, const KoInputDevice &id) : activeTool(0), canvas(cc), inputDevice(id), dummyToolWidget(0), dummyToolLabel(0) { } ~CanvasData() { // the dummy tool widget does not necessarily have a parent and we create it, so we delete it. delete dummyToolWidget; } void activateToolActions() { disabledDisabledActions.clear(); disabledActions.clear(); disabledCanvasShortcuts.clear(); // we do several things here // 1. enable the actions of the active tool // 2. disable conflicting actions // 3. replace conflicting actions in the action collection KActionCollection *canvasActionCollection = canvas->actionCollection(); QHash toolActions = activeTool->actions(); QHash::const_iterator it(toolActions.constBegin()); for (; it != toolActions.constEnd(); ++it) { if (canvasActionCollection) { QString toolActionID = it.key(); QAction *toolAction = it.value(); QAction * action = qobject_cast(canvasActionCollection->action(it.key())); if (action) { canvasActionCollection->takeAction(action); if (action != it.value()) { if (action->isEnabled()) { action->setEnabled(false); disabledActions.append(action); } else { disabledDisabledActions.append(action); } } } Q_FOREACH (QAction *a, canvasActionCollection->actions()) { QAction *canvasAction = dynamic_cast(a); if (canvasAction && canvasAction->shortcut().toString() != "" && canvasAction->shortcut() == toolAction->shortcut()) { warnFlake << activeToolId << ": action" << toolActionID << "conflicts with canvas action" << canvasAction->objectName() << "shortcut:" << canvasAction->shortcut().toString(); disabledCanvasShortcuts[canvasAction] = canvasAction->shortcut().toString(); canvasAction->setShortcut(QKeySequence()); } } canvasActionCollection->addAction(toolActionID, toolAction); } it.value()->setEnabled(true); } canvasActionCollection->readSettings(); // The shortcuts might have been configured in the meantime. } void deactivateToolActions() { if (!activeTool) return; // disable actions of active tool Q_FOREACH (QAction *action, activeTool->actions()) { action->setEnabled(false); } // enable actions which where disabled on activating the active tool // and re-add them to the action collection KActionCollection *ac = canvas->actionCollection(); Q_FOREACH (QPointer action, disabledDisabledActions) { if (action) { if (ac) { ac->addAction(action->objectName(), action); } } } disabledDisabledActions.clear(); Q_FOREACH (QPointer action, disabledActions) { if (action) { action->setEnabled(true); if(ac) { ac->addAction(action->objectName(), action); } } } disabledActions.clear(); QMap, QString>::const_iterator it(disabledCanvasShortcuts.constBegin()); for (; it != disabledCanvasShortcuts.constEnd(); ++it) { QAction *action = it.key(); QString shortcut = it.value(); action->setShortcut(shortcut); } disabledCanvasShortcuts.clear(); } KoToolBase *activeTool; // active Tool QString activeToolId; // the id of the active Tool QString activationShapeId; // the shape-type (KoShape::shapeId()) the activeTool 'belongs' to. QHash allTools; // all the tools that are created for this canvas. QStack stack; // stack of temporary tools KoCanvasController *const canvas; const KoInputDevice inputDevice; QWidget *dummyToolWidget; // the widget shown in the toolDocker. QLabel *dummyToolLabel; QList > disabledActions; ///< disabled conflicting actions QList > disabledDisabledActions; ///< disabled conflicting actions that were already disabled QMap, QString> disabledCanvasShortcuts; ///< Shortcuts that were temporarily removed from canvas actions because the tool overrides }; // ******** KoToolManager ********** KoToolManager::KoToolManager() : QObject(), d(new Private(this)) { connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(movedFocus(QWidget*, QWidget*))); } KoToolManager::~KoToolManager() { delete d; } QList KoToolManager::toolActionList() const { QList answer; answer.reserve(d->tools.count()); Q_FOREACH (ToolHelper *tool, d->tools) { if (tool->id() == KoCreateShapesTool_ID) continue; // don't show this one. answer.append(tool->toolAction()); } return answer; } void KoToolManager::requestToolActivation(KoCanvasController * controller) { if (d->canvasses.contains(controller)) { QString activeToolId = d->canvasses.value(controller).first()->activeToolId; Q_FOREACH (ToolHelper * th, d->tools) { if (th->id() == activeToolId) { d->toolActivated(th); break; } } } } KoInputDevice KoToolManager::currentInputDevice() const { return d->inputDevice; } void KoToolManager::registerToolActions(KActionCollection *ac, KoCanvasController *controller) { Q_ASSERT(controller); Q_ASSERT(ac); d->setup(); if (!d->canvasses.contains(controller)) { return; } // Actions available during the use of individual tools CanvasData *cd = d->canvasses.value(controller).first(); Q_FOREACH (KoToolBase *tool, cd->allTools) { QHash actions = tool->actions(); QHash::const_iterator action(actions.constBegin()); for (; action != actions.constEnd(); ++action) { if (!ac->action(action.key())) ac->addAction(action.key(), action.value()); } } // Actions used to switch tools via shortcuts Q_FOREACH (ToolHelper * th, d->tools) { if (ac->action(th->id())) { continue; } ShortcutToolAction* action = th->createShortcutToolAction(ac); ac->addCategorizedAction(th->id(), action, "tool-shortcuts"); } } void KoToolManager::addController(KoCanvasController *controller) { Q_ASSERT(controller); if (d->canvasses.contains(controller)) return; d->setup(); d->attachCanvas(controller); connect(controller->proxyObject, SIGNAL(destroyed(QObject*)), this, SLOT(attemptCanvasControllerRemoval(QObject*))); connect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*))); connect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*))); } void KoToolManager::removeCanvasController(KoCanvasController *controller) { Q_ASSERT(controller); disconnect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*))); disconnect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*))); d->detachCanvas(controller); } void KoToolManager::attemptCanvasControllerRemoval(QObject* controller) { KoCanvasControllerProxyObject* controllerActual = qobject_cast(controller); if (controllerActual) { removeCanvasController(controllerActual->canvasController()); } } void KoToolManager::updateShapeControllerBase(KoShapeBasedDocumentBase *shapeController, KoCanvasController *canvasController) { if (!d->canvasses.contains(canvasController)) return; QList canvasses = d->canvasses[canvasController]; Q_FOREACH (CanvasData *canvas, canvasses) { Q_FOREACH (KoToolBase *tool, canvas->allTools.values()) { tool->updateShapeController(shapeController); } } } void KoToolManager::switchToolRequested(const QString & id) { Q_ASSERT(d->canvasData); if (!d->canvasData) return; while (!d->canvasData->stack.isEmpty()) // switching means to flush the stack d->canvasData->stack.pop(); d->switchTool(id, false); } void KoToolManager::switchInputDeviceRequested(const KoInputDevice &id) { if (!d->canvasData) return; d->switchInputDevice(id); } void KoToolManager::switchToolTemporaryRequested(const QString &id) { d->switchTool(id, true); } void KoToolManager::switchBackRequested() { if (!d->canvasData) return; if (d->canvasData->stack.isEmpty()) { // default to changing to the interactionTool d->switchTool(KoInteractionTool_ID, false); return; } d->switchTool(d->canvasData->stack.pop(), false); } KoCreateShapesTool * KoToolManager::shapeCreatorTool(KoCanvasBase *canvas) const { Q_ASSERT(canvas); Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) { if (controller->canvas() == canvas) { KoCreateShapesTool *createTool = dynamic_cast (d->canvasData->allTools.value(KoCreateShapesTool_ID)); Q_ASSERT(createTool /* ID changed? */); return createTool; } } Q_ASSERT(0); // this should not happen return 0; } KoToolBase *KoToolManager::toolById(KoCanvasBase *canvas, const QString &id) const { Q_ASSERT(canvas); Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) { if (controller->canvas() == canvas) return d->canvasData->allTools.value(id); } return 0; } KoCanvasController *KoToolManager::activeCanvasController() const { if (! d->canvasData) return 0; return d->canvasData->canvas; } QString KoToolManager::preferredToolForSelection(const QList &shapes) { QList types; - Q_FOREACH (KoShape *shape, shapes) - if (! types.contains(shape->shapeId())) - types.append(shape->shapeId()); + Q_FOREACH (KoShape *shape, shapes) { + types << shape->shapeId(); + } + + KritaUtils::makeContainerUnique(types); QString toolType = KoInteractionTool_ID; int prio = INT_MAX; Q_FOREACH (ToolHelper *helper, d->tools) { + if (helper->id() == KoCreateShapesTool_ID) continue; + if (helper->priority() >= prio) continue; bool toolWillWork = false; foreach (const QString &type, types) { if (helper->activationShapeId().split(',').contains(type)) { toolWillWork = true; break; } } if (toolWillWork) { toolType = helper->id(); prio = helper->priority(); } } return toolType; } void KoToolManager::addDeferredToolFactory(KoToolFactoryBase *toolFactory) { ToolHelper *tool = new ToolHelper(toolFactory); // make sure all plugins are loaded as otherwise we will not load them d->setup(); d->tools.append(tool); // connect to all tools so we can hear their button-clicks connect(tool, SIGNAL(toolActivated(ToolHelper*)), this, SLOT(toolActivated(ToolHelper*))); // now create tools for all existing canvases Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) { // this canvascontroller is unknown, which is weird if (!d->canvasses.contains(controller)) { continue; } // create a tool for all canvasdata objects (i.e., all input devices on this canvas) foreach (CanvasData *cd, d->canvasses[controller]) { QPair toolPair = createTools(controller, tool); if (toolPair.second) { cd->allTools.insert(toolPair.first, toolPair.second); } } // Then create a button for the toolbox for this canvas if (tool->id() == KoCreateShapesTool_ID) { continue; } emit addedTool(tool->toolAction(), controller); } } QPair KoToolManager::createTools(KoCanvasController *controller, ToolHelper *tool) { // XXX: maybe this method should go into the private class? QHash origHash; if (d->canvasses.contains(controller)) { origHash = d->canvasses.value(controller).first()->allTools; } if (origHash.contains(tool->id())) { return QPair(tool->id(), origHash.value(tool->id())); } debugFlake << "Creating tool" << tool->id() << ". Activated on:" << tool->activationShapeId() << ", prio:" << tool->priority(); KoToolBase *tl = tool->createTool(controller->canvas()); if (tl) { d->uniqueToolIds.insert(tl, tool->uniqueId()); tl->setObjectName(tool->id()); Q_FOREACH (QAction *action, tl->actions()) { action->setEnabled(false); } } KoZoomTool *zoomTool = dynamic_cast(tl); if (zoomTool) { zoomTool->setCanvasController(controller); } KoPanTool *panTool = dynamic_cast(tl); if (panTool) { panTool->setCanvasController(controller); } return QPair(tool->id(), tl); } // NOT IMPLEMENTED void KoToolManager::updateToolShortcuts() { // auto actionRegistry = KisActionRegistry::instance(); // foreach (KoToolBase *t, allTools) { // for (auto it = t->actions().constBegin(); // it != t->actions().constEnd(); // ++it;) { // actionRegistry->updateShortcut(it.key(), it.value()); // } // } } void KoToolManager::initializeCurrentToolForCanvas() { d->postSwitchTool(false); } KoToolManager* KoToolManager::instance() { return s_instance; } QString KoToolManager::activeToolId() const { if (!d->canvasData) return QString(); return d->canvasData->activeToolId; } KoToolManager::Private *KoToolManager::priv() { return d; } /**** KoToolManager::Private ****/ KoToolManager::Private::Private(KoToolManager *qq) : q(qq), canvasData(0), layerExplicitlyDisabled(false) { } KoToolManager::Private::~Private() { qDeleteAll(tools); } // helper method. CanvasData *KoToolManager::Private::createCanvasData(KoCanvasController *controller, const KoInputDevice &device) { QHash toolsHash; Q_FOREACH (ToolHelper *tool, tools) { QPair toolPair = q->createTools(controller, tool); if (toolPair.second) { // only if a real tool was created toolsHash.insert(toolPair.first, toolPair.second); } } KoCreateShapesTool *createShapesTool = dynamic_cast(toolsHash.value(KoCreateShapesTool_ID)); Q_ASSERT(createShapesTool); QString id = KoShapeRegistry::instance()->keys()[0]; createShapesTool->setShapeId(id); CanvasData *cd = new CanvasData(controller, device); cd->allTools = toolsHash; return cd; } void KoToolManager::Private::setup() { if (tools.size() > 0) return; KoShapeRegistry::instance(); KoToolRegistry *registry = KoToolRegistry::instance(); Q_FOREACH (const QString & id, registry->keys()) { ToolHelper *t = new ToolHelper(registry->value(id)); tools.append(t); } // connect to all tools so we can hear their button-clicks Q_FOREACH (ToolHelper *tool, tools) connect(tool, SIGNAL(toolActivated(ToolHelper*)), q, SLOT(toolActivated(ToolHelper*))); // load pluggable input devices KoInputDeviceHandlerRegistry::instance(); } void KoToolManager::Private::connectActiveTool() { if (canvasData->activeTool) { connect(canvasData->activeTool, SIGNAL(cursorChanged(const QCursor &)), q, SLOT(updateCursor(const QCursor &))); connect(canvasData->activeTool, SIGNAL(activateTool(const QString &)), q, SLOT(switchToolRequested(const QString &))); connect(canvasData->activeTool, SIGNAL(activateTemporary(const QString &)), q, SLOT(switchToolTemporaryRequested(const QString &))); connect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested())); connect(canvasData->activeTool, SIGNAL(statusTextChanged(const QString &)), q, SIGNAL(changedStatusText(const QString &))); } // we expect the tool to emit a cursor on activation. updateCursor(Qt::ForbiddenCursor); } void KoToolManager::Private::disconnectActiveTool() { if (canvasData->activeTool) { canvasData->deactivateToolActions(); // repaint the decorations before we deactivate the tool as it might deleted // data needed for the repaint canvasData->activeTool->deactivate(); disconnect(canvasData->activeTool, SIGNAL(cursorChanged(const QCursor&)), q, SLOT(updateCursor(const QCursor&))); disconnect(canvasData->activeTool, SIGNAL(activateTool(const QString &)), q, SLOT(switchToolRequested(const QString &))); disconnect(canvasData->activeTool, SIGNAL(activateTemporary(const QString &)), q, SLOT(switchToolTemporaryRequested(const QString &))); disconnect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested())); disconnect(canvasData->activeTool, SIGNAL(statusTextChanged(const QString &)), q, SIGNAL(changedStatusText(const QString &))); } // emit a empty status text to clear status text from last active tool emit q->changedStatusText(QString()); } void KoToolManager::Private::switchTool(KoToolBase *tool, bool temporary) { Q_ASSERT(tool); if (canvasData == 0) return; if (canvasData->activeTool == tool && tool->toolId() != KoInteractionTool_ID) return; disconnectActiveTool(); canvasData->activeTool = tool; connectActiveTool(); postSwitchTool(temporary); } void KoToolManager::Private::switchTool(const QString &id, bool temporary) { Q_ASSERT(canvasData); if (!canvasData) return; if (canvasData->activeTool && temporary) canvasData->stack.push(canvasData->activeToolId); canvasData->activeToolId = id; KoToolBase *tool = canvasData->allTools.value(id); if (! tool) { return; } Q_FOREACH (ToolHelper *th, tools) { if (th->id() == id) { canvasData->activationShapeId = th->activationShapeId(); break; } } switchTool(tool, temporary); } void KoToolManager::Private::postSwitchTool(bool temporary) { #ifndef NDEBUG int canvasCount = 1; Q_FOREACH (QList list, canvasses) { bool first = true; Q_FOREACH (CanvasData *data, list) { if (first) { debugFlake << "Canvas" << canvasCount++; } debugFlake << " +- Tool:" << data->activeToolId << (data == canvasData ? " *" : ""); first = false; } } #endif Q_ASSERT(canvasData); if (!canvasData) return; KoToolBase::ToolActivation toolActivation; if (temporary) toolActivation = KoToolBase::TemporaryActivation; else toolActivation = KoToolBase::DefaultActivation; QSet shapesToOperateOn; if (canvasData->activeTool && canvasData->activeTool->canvas() && canvasData->activeTool->canvas()->shapeManager()) { KoSelection *selection = canvasData->activeTool->canvas()->shapeManager()->selection(); Q_ASSERT(selection); shapesToOperateOn = QSet::fromList(selection->selectedEditableShapesAndDelegates()); } if (canvasData->canvas->canvas()) { // Caller of postSwitchTool expect this to be called to update the selected tool updateToolForProxy(); canvasData->activeTool->activate(toolActivation, shapesToOperateOn); KoCanvasBase *canvas = canvasData->canvas->canvas(); canvas->updateInputMethodInfo(); } else { canvasData->activeTool->activate(toolActivation, shapesToOperateOn); } QList > optionWidgetList = canvasData->activeTool->optionWidgets(); if (optionWidgetList.empty()) { // no option widget. QWidget *toolWidget; QString title; Q_FOREACH (ToolHelper *tool, tools) { if (tool->id() == canvasData->activeTool->toolId()) { title = tool->toolTip(); break; } } toolWidget = canvasData->dummyToolWidget; if (toolWidget == 0) { toolWidget = new QWidget(); toolWidget->setObjectName("DummyToolWidget"); QVBoxLayout *layout = new QVBoxLayout(toolWidget); layout->setMargin(3); canvasData->dummyToolLabel = new QLabel(toolWidget); layout->addWidget(canvasData->dummyToolLabel); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding)); toolWidget->setLayout(layout); canvasData->dummyToolWidget = toolWidget; } canvasData->dummyToolLabel->setText(i18n("Active tool: %1", title)); optionWidgetList.append(toolWidget); } // Activate the actions for the currently active tool canvasData->activateToolActions(); emit q->changedTool(canvasData->canvas, uniqueToolIds.value(canvasData->activeTool)); emit q->toolOptionWidgetsChanged(canvasData->canvas, optionWidgetList); } void KoToolManager::Private::switchCanvasData(CanvasData *cd) { Q_ASSERT(cd); KoCanvasBase *oldCanvas = 0; KoInputDevice oldInputDevice; if (canvasData) { oldCanvas = canvasData->canvas->canvas(); oldInputDevice = canvasData->inputDevice; if (canvasData->activeTool) { disconnectActiveTool(); } KoToolProxy *proxy = proxies.value(oldCanvas); Q_ASSERT(proxy); proxy->setActiveTool(0); } canvasData = cd; inputDevice = canvasData->inputDevice; if (canvasData->activeTool) { connectActiveTool(); postSwitchTool(false); } if (oldInputDevice != canvasData->inputDevice) { emit q->inputDeviceChanged(canvasData->inputDevice); } if (oldCanvas != canvasData->canvas->canvas()) { emit q->changedCanvas(canvasData->canvas->canvas()); } } void KoToolManager::Private::toolActivated(ToolHelper *tool) { Q_ASSERT(tool); Q_ASSERT(canvasData); if (!canvasData) return; KoToolBase *t = canvasData->allTools.value(tool->id()); Q_ASSERT(t); canvasData->activeToolId = tool->id(); canvasData->activationShapeId = tool->activationShapeId(); switchTool(t, false); } void KoToolManager::Private::detachCanvas(KoCanvasController *controller) { Q_ASSERT(controller); // check if we are removing the active canvas controller if (canvasData && canvasData->canvas == controller) { KoCanvasController *newCanvas = 0; // try to find another canvas controller beside the one we are removing Q_FOREACH (KoCanvasController* canvas, canvasses.keys()) { if (canvas != controller) { // yay found one newCanvas = canvas; break; } } if (newCanvas) { switchCanvasData(canvasses.value(newCanvas).first()); } else { emit q->toolOptionWidgetsChanged(controller, QList >()); // as a last resort just set a blank one canvasData = 0; } } KoToolProxy *proxy = proxies.value(controller->canvas()); if (proxy) proxy->setActiveTool(0); QList tools; Q_FOREACH (CanvasData *canvasData, canvasses.value(controller)) { Q_FOREACH (KoToolBase *tool, canvasData->allTools) { if (! tools.contains(tool)) { tools.append(tool); } } delete canvasData; } Q_FOREACH (KoToolBase *tool, tools) { uniqueToolIds.remove(tool); delete tool; } canvasses.remove(controller); emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0); } void KoToolManager::Private::attachCanvas(KoCanvasController *controller) { Q_ASSERT(controller); CanvasData *cd = createCanvasData(controller, KoInputDevice::mouse()); // switch to new canvas as the active one. switchCanvasData(cd); inputDevice = cd->inputDevice; QList canvasses_; canvasses_.append(cd); canvasses[controller] = canvasses_; KoToolProxy *tp = proxies[controller->canvas()]; if (tp) tp->priv()->setCanvasController(controller); if (cd->activeTool == 0) { // no active tool, so we activate the highest priority main tool int highestPriority = INT_MAX; ToolHelper * helper = 0; Q_FOREACH (ToolHelper * th, tools) { if (th->section() == KoToolFactoryBase::mainToolType()) { if (th->priority() < highestPriority) { highestPriority = qMin(highestPriority, th->priority()); helper = th; } } } if (helper) toolActivated(helper); } Connector *connector = new Connector(controller->canvas()->shapeManager()); connect(connector, SIGNAL(selectionChanged(QList)), q, SLOT(selectionChanged(QList))); connect(controller->canvas()->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), q, SLOT(currentLayerChanged(const KoShapeLayer*))); emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0); } void KoToolManager::Private::movedFocus(QWidget *from, QWidget *to) { Q_UNUSED(from); // no canvas anyway or no focus set anyway? if (!canvasData || to == 0) { return; } // Check if this app is about QWidget-based KoCanvasControllerWidget canvasses // XXX: Focus handling for non-qwidget based canvases! KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast(canvasData->canvas); if (!canvasControllerWidget) { return; } // canvasWidget is set as focusproxy for KoCanvasControllerWidget, // so all focus checks are to be done against canvasWidget objects // focus returned to current canvas? if (to == canvasData->canvas->canvas()->canvasWidget()) { // nothing to do return; } // if the 'to' is one of our canvasWidgets, then switch. // for code simplicity the current canvas will be checked again, // but would have been catched already in the lines above, so no issue KoCanvasController *newCanvas = 0; Q_FOREACH (KoCanvasController* canvas, canvasses.keys()) { if (canvas->canvas()->canvasWidget() == to) { newCanvas = canvas; break; } } // none of our canvasWidgets got focus? if (newCanvas == 0) { return; } // switch to canvasdata matching inputdevice used last with this app instance Q_FOREACH (CanvasData *data, canvasses.value(newCanvas)) { if (data->inputDevice == inputDevice) { switchCanvasData(data); return; } } // if no such inputDevice for this canvas, then simply fallback to first one switchCanvasData(canvasses.value(newCanvas).first()); } void KoToolManager::Private::updateCursor(const QCursor &cursor) { Q_ASSERT(canvasData); Q_ASSERT(canvasData->canvas); Q_ASSERT(canvasData->canvas->canvas()); canvasData->canvas->canvas()->setCursor(cursor); } void KoToolManager::Private::selectionChanged(const QList &shapes) { QList types; Q_FOREACH (KoShape *shape, shapes) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { // no delegates, just the orig shape delegates << shape; } foreach (KoShape *shape2, delegates) { Q_ASSERT(shape2); if (! types.contains(shape2->shapeId())) { types.append(shape2->shapeId()); } } } // check if there is still a shape selected the active tool can work on // there needs to be at least one shape for a tool without an activationShapeId // to work // if not change the current tool to the default tool if (!(canvasData->activationShapeId.isNull() && shapes.size() > 0) && canvasData->activationShapeId != "flake/always" && canvasData->activationShapeId != "flake/edit") { bool currentToolWorks = false; foreach (const QString &type, types) { if (canvasData->activationShapeId.split(',').contains(type)) { currentToolWorks = true; break; } } if (!currentToolWorks) { switchTool(KoInteractionTool_ID, false); } } emit q->toolCodesSelected(types); } void KoToolManager::Private::currentLayerChanged(const KoShapeLayer *layer) { emit q->currentLayerChanged(canvasData->canvas, layer); layerExplicitlyDisabled = layer && !layer->isEditable(); updateToolForProxy(); debugFlake << "Layer changed to" << layer << "explicitly disabled:" << layerExplicitlyDisabled; } void KoToolManager::Private::updateToolForProxy() { KoToolProxy *proxy = proxies.value(canvasData->canvas->canvas()); if(!proxy) return; bool canUseTool = !layerExplicitlyDisabled || canvasData->activationShapeId.endsWith(QLatin1String("/always")); proxy->setActiveTool(canUseTool ? canvasData->activeTool : 0); } void KoToolManager::Private::switchInputDevice(const KoInputDevice &device) { Q_ASSERT(canvasData); if (!canvasData) return; if (inputDevice == device) return; if (inputDevice.isMouse() && device.isMouse()) return; if (device.isMouse() && !inputDevice.isMouse()) { // we never switch back to mouse from a tablet input device, so the user can use the // mouse to edit the settings for a tool activated by a tablet. See bugs // https://bugs.kde.org/show_bug.cgi?id=283130 and https://bugs.kde.org/show_bug.cgi?id=285501. // We do continue to switch between tablet devices, thought. return; } QList items = canvasses[canvasData->canvas]; // disable all actions for all tools in the all canvasdata objects for this canvas. Q_FOREACH (CanvasData *cd, items) { Q_FOREACH (KoToolBase* tool, cd->allTools) { Q_FOREACH (QAction * action, tool->actions()) { action->setEnabled(false); } } } // search for a canvasdata object for the current input device Q_FOREACH (CanvasData *cd, items) { if (cd->inputDevice == device) { switchCanvasData(cd); if (!canvasData->activeTool) { switchTool(KoInteractionTool_ID, false); } return; } } // still here? That means we need to create a new CanvasData instance with the current InputDevice. CanvasData *cd = createCanvasData(canvasData->canvas, device); // switch to new canvas as the active one. QString oldTool = canvasData->activeToolId; items.append(cd); canvasses[cd->canvas] = items; switchCanvasData(cd); q->switchToolRequested(oldTool); } void KoToolManager::Private::registerToolProxy(KoToolProxy *proxy, KoCanvasBase *canvas) { proxies.insert(canvas, proxy); Q_FOREACH (KoCanvasController *controller, canvasses.keys()) { if (controller->canvas() == canvas) { proxy->priv()->setCanvasController(controller); break; } } } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoToolManager.cpp" diff --git a/libs/flake/KoToolProxy.cpp b/libs/flake/KoToolProxy.cpp index 0b71e3580a..e69760ff32 100644 --- a/libs/flake/KoToolProxy.cpp +++ b/libs/flake/KoToolProxy.cpp @@ -1,601 +1,707 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (c) 2006-2011 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 "KoToolProxy.h" #include "KoToolProxy_p.h" #include #include #include #include #include #include #include #include #include #include #include "KoToolBase.h" #include "KoPointerEvent.h" #include "KoInputDevice.h" #include "KoToolManager_p.h" #include "KoToolSelection.h" #include "KoCanvasBase.h" #include "KoCanvasController.h" #include "KoShapeManager.h" #include "KoSelection.h" #include "KoShapeLayer.h" #include "KoShapePaste.h" #include "KoShapeRegistry.h" #include "KoShapeController.h" #include "KoOdf.h" #include "KoViewConverter.h" #include "KoShapeFactoryBase.h" +#include +#include +#include "kis_algebra_2d.h" +#include +#include + KoToolProxyPrivate::KoToolProxyPrivate(KoToolProxy *p) : activeTool(0), tabletPressed(false), hasSelection(false), controller(0), parent(p) { scrollTimer.setInterval(100); mouseLeaveWorkaround = false; multiClickCount = 0; } void KoToolProxyPrivate::timeout() // Auto scroll the canvas { Q_ASSERT(controller); QPoint offset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY()); QPoint origin = controller->canvas()->documentOrigin(); QPoint viewPoint = widgetScrollPoint - origin - offset; QRectF mouseArea(viewPoint, QSizeF(10, 10)); mouseArea.setTopLeft(mouseArea.center()); controller->ensureVisible(mouseArea, true); QPoint newOffset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY()); QPoint moved = offset - newOffset; if (moved.isNull()) return; widgetScrollPoint += moved; QPointF documentPoint = parent->widgetToDocument(widgetScrollPoint); QMouseEvent event(QEvent::MouseMove, widgetScrollPoint, Qt::LeftButton, Qt::LeftButton, 0); KoPointerEvent ev(&event, documentPoint); activeTool->mouseMoveEvent(&ev); } void KoToolProxyPrivate::checkAutoScroll(const KoPointerEvent &event) { if (controller == 0) return; if (!activeTool) return; if (!activeTool->wantsAutoScroll()) return; if (!event.isAccepted()) return; if (event.buttons() != Qt::LeftButton) return; widgetScrollPoint = event.pos(); if (! scrollTimer.isActive()) scrollTimer.start(); } void KoToolProxyPrivate::selectionChanged(bool newSelection) { if (hasSelection == newSelection) return; hasSelection = newSelection; emit parent->selectionChanged(hasSelection); } bool KoToolProxyPrivate::isActiveLayerEditable() { if (!activeTool) return false; KoShapeManager * shapeManager = activeTool->canvas()->shapeManager(); KoShapeLayer * activeLayer = shapeManager->selection()->activeLayer(); if (activeLayer && !activeLayer->isEditable()) return false; return true; } KoToolProxy::KoToolProxy(KoCanvasBase *canvas, QObject *parent) : QObject(parent), d(new KoToolProxyPrivate(this)) { KoToolManager::instance()->priv()->registerToolProxy(this, canvas); connect(&d->scrollTimer, SIGNAL(timeout()), this, SLOT(timeout())); } KoToolProxy::~KoToolProxy() { delete d; } void KoToolProxy::paint(QPainter &painter, const KoViewConverter &converter) { if (d->activeTool) d->activeTool->paint(painter, converter); } void KoToolProxy::repaintDecorations() { if (d->activeTool) d->activeTool->repaintDecorations(); } QPointF KoToolProxy::widgetToDocument(const QPointF &widgetPoint) const { QPoint offset = QPoint(d->controller->canvasOffsetX(), d->controller->canvasOffsetY()); QPoint origin = d->controller->canvas()->documentOrigin(); QPointF viewPoint = widgetPoint.toPoint() - QPointF(origin - offset); return d->controller->canvas()->viewConverter()->viewToDocument(viewPoint); } KoCanvasBase* KoToolProxy::canvas() const { return d->controller->canvas(); } void KoToolProxy::touchEvent(QTouchEvent *event) { QPointF point; QList touchPoints; bool isPrimary = true; Q_FOREACH (QTouchEvent::TouchPoint p, event->touchPoints()) { QPointF docPoint = widgetToDocument(p.screenPos()); if (isPrimary) { point = docPoint; isPrimary = false; } KoTouchPoint touchPoint; touchPoint.touchPoint = p; touchPoint.point = point; touchPoint.lastPoint = widgetToDocument(p.lastNormalizedPos()); touchPoints << touchPoint; } KoPointerEvent ev(event, point, touchPoints); KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); switch (event->type()) { case QEvent::TouchBegin: ev.setTabletButton(Qt::LeftButton); if (d->activeTool) { if( d->activeTool->wantsTouch() ) d->activeTool->touchEvent(event); else d->activeTool->mousePressEvent(&ev); } break; case QEvent::TouchUpdate: ev.setTabletButton(Qt::LeftButton); if (d->activeTool) { if( d->activeTool->wantsTouch() ) d->activeTool->touchEvent(event); else d->activeTool->mouseMoveEvent(&ev); } break; case QEvent::TouchEnd: ev.setTabletButton(Qt::LeftButton); if (d->activeTool) { if( d->activeTool->wantsTouch() ) d->activeTool->touchEvent(event); else d->activeTool->mouseReleaseEvent(&ev); } break; default: ; // ingore the rest } d->mouseLeaveWorkaround = true; } void KoToolProxy::tabletEvent(QTabletEvent *event, const QPointF &point) { // We get these events exclusively from KisToolProxy - accept them event->accept(); KoInputDevice id(event->device(), event->pointerType(), event->uniqueId()); KoToolManager::instance()->priv()->switchInputDevice(id); KoPointerEvent ev(event, point); switch (event->type()) { case QEvent::TabletPress: ev.setTabletButton(Qt::LeftButton); if (!d->tabletPressed && d->activeTool) d->activeTool->mousePressEvent(&ev); d->tabletPressed = true; break; case QEvent::TabletRelease: ev.setTabletButton(Qt::LeftButton); d->tabletPressed = false; d->scrollTimer.stop(); if (d->activeTool) d->activeTool->mouseReleaseEvent(&ev); break; case QEvent::TabletMove: if (d->tabletPressed) ev.setTabletButton(Qt::LeftButton); if (d->activeTool) d->activeTool->mouseMoveEvent(&ev); d->checkAutoScroll(ev); default: ; // ignore the rest. } d->mouseLeaveWorkaround = true; } void KoToolProxy::mousePressEvent(KoPointerEvent *ev) { d->mouseLeaveWorkaround = false; KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); d->mouseDownPoint = ev->pos(); if (d->tabletPressed) // refuse to send a press unless there was a release first. return; QPointF globalPoint = ev->globalPos(); if (d->multiClickGlobalPoint != globalPoint) { if (qAbs(globalPoint.x() - d->multiClickGlobalPoint.x()) > 5|| qAbs(globalPoint.y() - d->multiClickGlobalPoint.y()) > 5) { d->multiClickCount = 0; } d->multiClickGlobalPoint = globalPoint; } if (d->multiClickCount && d->multiClickTimeStamp.elapsed() < QApplication::doubleClickInterval()) { // One more multiclick; d->multiClickCount++; } else { d->multiClickTimeStamp.start(); d->multiClickCount = 1; } if (d->activeTool) { switch (d->multiClickCount) { case 0: case 1: d->activeTool->mousePressEvent(ev); break; case 2: d->activeTool->mouseDoubleClickEvent(ev); break; case 3: default: d->activeTool->mouseTripleClickEvent(ev); break; } } else { d->multiClickCount = 0; ev->ignore(); } } void KoToolProxy::mousePressEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mousePressEvent(&ev); } void KoToolProxy::mouseDoubleClickEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseDoubleClickEvent(&ev); } void KoToolProxy::mouseDoubleClickEvent(KoPointerEvent *event) { // let us handle it as any other mousepress (where we then detect multi clicks mousePressEvent(event); } void KoToolProxy::mouseMoveEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseMoveEvent(&ev); } void KoToolProxy::mouseMoveEvent(KoPointerEvent *event) { if (d->mouseLeaveWorkaround) { d->mouseLeaveWorkaround = false; return; } KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); if (d->activeTool == 0) { event->ignore(); return; } d->activeTool->mouseMoveEvent(event); d->checkAutoScroll(*event); } void KoToolProxy::mouseReleaseEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseReleaseEvent(&ev); } void KoToolProxy::mouseReleaseEvent(KoPointerEvent* event) { d->mouseLeaveWorkaround = false; KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); d->scrollTimer.stop(); if (d->activeTool) { d->activeTool->mouseReleaseEvent(event); } else { event->ignore(); } } void KoToolProxy::keyPressEvent(QKeyEvent *event) { if (d->activeTool) d->activeTool->keyPressEvent(event); else event->ignore(); } void KoToolProxy::keyReleaseEvent(QKeyEvent *event) { if (d->activeTool) d->activeTool->keyReleaseEvent(event); else event->ignore(); } void KoToolProxy::wheelEvent(QWheelEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); if (d->activeTool) d->activeTool->wheelEvent(&ev); else event->ignore(); } void KoToolProxy::wheelEvent(KoPointerEvent *event) { if (d->activeTool) d->activeTool->wheelEvent(event); else event->ignore(); } QVariant KoToolProxy::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const { if (d->activeTool) return d->activeTool->inputMethodQuery(query, converter); return QVariant(); } void KoToolProxy::inputMethodEvent(QInputMethodEvent *event) { if (d->activeTool) d->activeTool->inputMethodEvent(event); } QMenu *KoToolProxy::popupActionsMenu() { return d->activeTool ? d->activeTool->popupActionsMenu() : 0; } void KoToolProxy::setActiveTool(KoToolBase *tool) { if (d->activeTool) disconnect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool))); d->activeTool = tool; if (tool) { connect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool))); d->selectionChanged(hasSelection()); emit toolChanged(tool->toolId()); } } void KoToolProxyPrivate::setCanvasController(KoCanvasController *c) { controller = c; } QHash KoToolProxy::actions() const { return d->activeTool ? d->activeTool->actions() : QHash(); } bool KoToolProxy::hasSelection() const { return d->activeTool ? d->activeTool->hasSelection() : false; } void KoToolProxy::cut() { if (d->activeTool && d->isActiveLayerEditable()) d->activeTool->cut(); } void KoToolProxy::copy() const { if (d->activeTool) d->activeTool->copy(); } -bool KoToolProxy::paste() + +namespace { +QPointF getFittingOffset(QList shapes, + const QPointF &shapesOffset, + const QRectF &documentRect, + const qreal fitRatio) +{ + QPointF accumulatedFitOffset; + + Q_FOREACH (KoShape *shape, shapes) { + const QRectF bounds = shape->boundingRect(); + + const QPointF center = bounds.center() + shapesOffset; + + const qreal wMargin = (0.5 - fitRatio) * bounds.width(); + const qreal hMargin = (0.5 - fitRatio) * bounds.height(); + const QRectF allowedRect = documentRect.adjusted(-wMargin, -hMargin, wMargin, hMargin); + + const QPointF fittedCenter = KisAlgebra2D::clampPoint(center, allowedRect); + + accumulatedFitOffset += fittedCenter - center; + } + + return accumulatedFitOffset; +} +} + +bool KoToolProxy::paste(bool pasteAtCursorPosition) { bool success = false; KoCanvasBase *canvas = d->controller->canvas(); - if (d->activeTool && d->isActiveLayerEditable()) + if (d->activeTool && d->isActiveLayerEditable()) { success = d->activeTool->paste(); + } - if (!success) { - const QMimeData *data = QApplication::clipboard()->mimeData(); + KoSvgPaste paste; + if (!success && paste.hasShapes()) { + QSizeF fragmentSize; + + QList shapes = + paste.fetchShapes(canvas->shapeController()->documentRectInPixels(), + canvas->shapeController()->pixelsPerInch(), &fragmentSize); - if (data->hasFormat(KoOdf::mimeType(KoOdf::Text))) { + if (!shapes.isEmpty()) { KoShapeManager *shapeManager = canvas->shapeManager(); - KoShapePaste paste(canvas, shapeManager->selection()->activeLayer()); - success = paste.paste(KoOdf::Text, data); - if (success) { - shapeManager->selection()->deselectAll(); - Q_FOREACH (KoShape *shape, paste.pastedShapes()) { - shapeManager->selection()->select(shape); + shapeManager->selection()->deselectAll(); + + KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Paste shapes")); + + Q_FOREACH (KoShape *shape, shapes) { + canvas->shapeController()->addShapeDirect(shape, parentCommand); + } + + QPointF finalShapesOffset; + + + if (pasteAtCursorPosition) { + QRectF boundingRect = KoShape::boundingRect(shapes); + const QPointF cursorPos = canvas->canvasController()->currentCursorPosition(); + finalShapesOffset = cursorPos - boundingRect.center(); + + } else { + bool foundOverlapping = false; + + QRectF boundingRect = KoShape::boundingRect(shapes); + const QPointF offsetStep = 0.1 * QPointF(boundingRect.width(), boundingRect.height()); + + QPointF offset; + + Q_FOREACH (KoShape *shape, shapes) { + QRectF br1 = shape->boundingRect(); + + bool hasOverlappingShape = false; + + do { + hasOverlappingShape = false; + + // we cannot use shapesAt() here, because the groups are not + // handled in the shape manager's tree + QList conflicts = shapeManager->shapes(); + + Q_FOREACH (KoShape *intersectedShape, conflicts) { + if (intersectedShape == shape) continue; + + QRectF br2 = intersectedShape->boundingRect(); + + const qreal tolerance = 2.0; /* pt */ + if (KisAlgebra2D::fuzzyCompareRects(br1, br2, tolerance)) { + br1.translate(offsetStep.x(), offsetStep.y()); + offset += offsetStep; + + hasOverlappingShape = true; + foundOverlapping = true; + break; + } + } + } while (hasOverlappingShape); + + if (foundOverlapping) break; + } + + if (foundOverlapping) { + finalShapesOffset = offset; } } + + const QRectF documentRect = canvas->shapeController()->documentRect(); + finalShapesOffset += getFittingOffset(shapes, finalShapesOffset, documentRect, 0.1); + + if (!finalShapesOffset.isNull()) { + new KoShapeMoveCommand(shapes, finalShapesOffset, parentCommand); + } + + canvas->addCommand(parentCommand); + + Q_FOREACH (KoShape *shape, shapes) { + canvas->selectedShapesProxy()->selection()->select(shape); + } + + success = true; } } if (!success) { const QMimeData *data = QApplication::clipboard()->mimeData(); QList imageList; QImage image = QApplication::clipboard()->image(); if (!image.isNull()) { imageList << image; } // QT5TODO: figure out how to download data synchronously, which is deprecated in frameworks. else if (data->hasUrls()) { QList urls = QApplication::clipboard()->mimeData()->urls(); foreach (const QUrl &url, urls) { QImage image; image.load(url.toLocalFile()); if (!image.isNull()) { imageList << image; } } } KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("PictureShape"); QWidget *canvasWidget = canvas->canvasWidget(); const KoViewConverter *converter = canvas->viewConverter(); if (imageList.length() > 0 && factory && canvasWidget) { KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Paste Image")); Q_FOREACH (const QImage &image, imageList) { if (!image.isNull()) { QPointF p = converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + canvas->canvasController()->documentOffset()- canvasWidget->pos()); KoProperties params; params.setProperty("qimage", image); KoShape *shape = factory->createShape(¶ms, canvas->shapeController()->resourceManager()); shape->setPosition(p); // add shape to the document canvas->shapeController()->addShapeDirect(shape, cmd); success = true; } } canvas->addCommand(cmd); } } return success; } void KoToolProxy::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { if (d->activeTool) d->activeTool->dragMoveEvent(event, point); } void KoToolProxy::dragLeaveEvent(QDragLeaveEvent *event) { if (d->activeTool) d->activeTool->dragLeaveEvent(event); } void KoToolProxy::dropEvent(QDropEvent *event, const QPointF &point) { if (d->activeTool) d->activeTool->dropEvent(event, point); } QStringList KoToolProxy::supportedPasteMimeTypes() const { if (d->activeTool) return d->activeTool->supportedPasteMimeTypes(); return QStringList(); } void KoToolProxy::deleteSelection() { if (d->activeTool) d->activeTool->deleteSelection(); } void KoToolProxy::processEvent(QEvent *e) const { if(e->type()==QEvent::ShortcutOverride && d->activeTool && d->activeTool->isInTextMode() && (static_cast(e)->modifiers()==Qt::NoModifier || static_cast(e)->modifiers()==Qt::ShiftModifier)) { e->accept(); } } void KoToolProxy::requestUndoDuringStroke() { if (d->activeTool) { d->activeTool->requestUndoDuringStroke(); } } void KoToolProxy::requestStrokeCancellation() { if (d->activeTool) { d->activeTool->requestStrokeCancellation(); } } void KoToolProxy::requestStrokeEnd() { if (d->activeTool) { d->activeTool->requestStrokeEnd(); } } KoToolProxyPrivate *KoToolProxy::priv() { return d; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoToolProxy.cpp" diff --git a/libs/flake/KoToolProxy.h b/libs/flake/KoToolProxy.h index 234fbdfe16..8adbad8e44 100644 --- a/libs/flake/KoToolProxy.h +++ b/libs/flake/KoToolProxy.h @@ -1,201 +1,201 @@ /* This file is part of the KDE project * * Copyright (c) 2006, 2010 Boudewijn Rempt * Copyright (C) 2006-2010 Thomas Zander * * 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 _KO_TOOL_PROXY_H_ #define _KO_TOOL_PROXY_H_ #include "kritaflake_export.h" #include #include class QAction; class QAction; class QMouseEvent; class QKeyEvent; class QWheelEvent; class QTabletEvent; class KoCanvasBase; class KoViewConverter; class KoToolBase; class KoToolProxyPrivate; class QInputMethodEvent; class KoPointerEvent; class QDragMoveEvent; class QDragLeaveEvent; class QDropEvent; class QTouchEvent; class QPainter; class QPointF; class QMenu; /** * Tool proxy object which allows an application to address the current tool. * * Applications typically have a canvas and a canvas requires a tool for * the user to do anything. Since the flake system is responsible for handling * tools and also to change the active tool when needed we provide one class * that can be used by an application canvas to route all the native events too * which will transparantly be routed to the active tool. Without the application * having to bother about which tool is active. */ class KRITAFLAKE_EXPORT KoToolProxy : public QObject { Q_OBJECT public: /** * Constructor * @param canvas Each canvas has 1 toolProxy. Pass the parent here. * @param parent a parent QObject for memory management purposes. */ explicit KoToolProxy(KoCanvasBase *canvas, QObject *parent = 0); virtual ~KoToolProxy(); /// Forwarded to the current KoToolBase void paint(QPainter &painter, const KoViewConverter &converter); /// Forwarded to the current KoToolBase void repaintDecorations(); /** * Forward the given touch event to the current KoToolBase. * The viewconverter and document offset are necessary to convert all * the QTouchPoints to KoTouchPoints that work in document coordinates. */ void touchEvent(QTouchEvent *event); /// Forwarded to the current KoToolBase void tabletEvent(QTabletEvent *event, const QPointF &point); /// Forwarded to the current KoToolBase void mousePressEvent(QMouseEvent *event, const QPointF &point); void mousePressEvent(KoPointerEvent *event); /// Forwarded to the current KoToolBase void mouseDoubleClickEvent(QMouseEvent *event, const QPointF &point); void mouseDoubleClickEvent(KoPointerEvent *event); /// Forwarded to the current KoToolBase void mouseMoveEvent(QMouseEvent *event, const QPointF &point); void mouseMoveEvent(KoPointerEvent *event); /// Forwarded to the current KoToolBase void mouseReleaseEvent(QMouseEvent *event, const QPointF &point); void mouseReleaseEvent(KoPointerEvent *event); /// Forwarded to the current KoToolBase void keyPressEvent(QKeyEvent *event); /// Forwarded to the current KoToolBase void keyReleaseEvent(QKeyEvent *event); /// Forwarded to the current KoToolBase void wheelEvent(QWheelEvent * event, const QPointF &point); void wheelEvent(KoPointerEvent *event); /// Forwarded to the current KoToolBase QVariant inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const; /// Forwarded to the current KoToolBase void inputMethodEvent(QInputMethodEvent *event); /// Forwarded to the current KoToolBase QMenu* popupActionsMenu(); /// Forwarded to the current KoToolBase void deleteSelection(); /// This method gives the proxy a chance to do things. for example it is need to have working singlekey /// shortcuts. call it from the canvas' event function and forward it to QWidget::event() later. void processEvent(QEvent *) const; /** * Retrieves the entire collection of actions for the active tool * or an empty hash if there is no active tool yet. */ QHash actions() const; /// returns true if the current tool holds a selection bool hasSelection() const; /// Forwarded to the current KoToolBase void cut(); /// Forwarded to the current KoToolBase void copy() const; /// Forwarded to the current KoToolBase - bool paste(); + bool paste(bool pasteAtCursorPosition = false); /// Forwarded to the current KoToolBase QStringList supportedPasteMimeTypes() const; /// Forwarded to the current KoToolBase void dragMoveEvent(QDragMoveEvent *event, const QPointF &point); /// Forwarded to the current KoToolBase void dragLeaveEvent(QDragLeaveEvent *event); /// Forwarded to the current KoToolBase void dropEvent(QDropEvent *event, const QPointF &point); /// Set the new active tool. virtual void setActiveTool(KoToolBase *tool); /// \internal KoToolProxyPrivate *priv(); protected Q_SLOTS: /// Forwarded to the current KoToolBase void requestUndoDuringStroke(); /// Forwarded to the current KoToolBase void requestStrokeCancellation(); /// Forwarded to the current KoToolBase void requestStrokeEnd(); Q_SIGNALS: /** * A tool can have a selection that is copy-able, this signal is emitted when that status changes. * @param hasSelection is true when the tool holds selected data. */ void selectionChanged(bool hasSelection); /** * Emitted every time a tool is changed. * @param toolId the id of the tool. * @see KoToolBase::toolId() */ void toolChanged(const QString &toolId); protected: virtual QPointF widgetToDocument(const QPointF &widgetPoint) const; KoCanvasBase* canvas() const; private: Q_PRIVATE_SLOT(d, void timeout()) Q_PRIVATE_SLOT(d, void selectionChanged(bool)) friend class KoToolProxyPrivate; KoToolProxyPrivate * const d; }; #endif // _KO_TOOL_PROXY_H_ diff --git a/libs/flake/commands/KoShapeMoveCommand.cpp b/libs/flake/commands/KoShapeMoveCommand.cpp index 86c2867ac8..b9a88e67f7 100644 --- a/libs/flake/commands/KoShapeMoveCommand.cpp +++ b/libs/flake/commands/KoShapeMoveCommand.cpp @@ -1,91 +1,104 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoShapeMoveCommand.h" #include #include #include "kis_command_ids.h" class Q_DECL_HIDDEN KoShapeMoveCommand::Private { public: QList shapes; QList previousPositions, newPositions; KoFlake::AnchorPosition anchor; }; KoShapeMoveCommand::KoShapeMoveCommand(const QList &shapes, QList &previousPositions, QList &newPositions, KoFlake::AnchorPosition anchor, KUndo2Command *parent) - : KUndo2Command(parent), + : KUndo2Command(kundo2_i18n("Move shapes"), parent), d(new Private()) { d->shapes = shapes; d->previousPositions = previousPositions; d->newPositions = newPositions; d->anchor = anchor; Q_ASSERT(d->shapes.count() == d->previousPositions.count()); Q_ASSERT(d->shapes.count() == d->newPositions.count()); +} + +KoShapeMoveCommand::KoShapeMoveCommand(const QList &shapes, const QPointF &offset, KUndo2Command *parent) + : KUndo2Command(kundo2_i18n("Move shapes"), parent), + d(new Private()) +{ + d->shapes = shapes; + d->anchor = KoFlake::Center; + + Q_FOREACH (KoShape *shape, d->shapes) { + const QPointF pos = shape->absolutePosition(); - setText(kundo2_i18n("Move shapes")); + d->previousPositions << pos; + d->newPositions << pos + offset; + } } KoShapeMoveCommand::~KoShapeMoveCommand() { delete d; } void KoShapeMoveCommand::redo() { KUndo2Command::redo(); for (int i = 0; i < d->shapes.count(); i++) { d->shapes.at(i)->update(); d->shapes.at(i)->setAbsolutePosition(d->newPositions.at(i), d->anchor); d->shapes.at(i)->update(); } } void KoShapeMoveCommand::undo() { KUndo2Command::undo(); for (int i = 0; i < d->shapes.count(); i++) { d->shapes.at(i)->update(); d->shapes.at(i)->setAbsolutePosition(d->previousPositions.at(i), d->anchor); d->shapes.at(i)->update(); } } int KoShapeMoveCommand::id() const { return KisCommandUtils::MoveShapeId; } bool KoShapeMoveCommand::mergeWith(const KUndo2Command *command) { const KoShapeMoveCommand *other = dynamic_cast(command); if (other->d->shapes != d->shapes || other->d->anchor != d->anchor) { return false; } d->newPositions = other->d->newPositions; return true; } diff --git a/libs/flake/commands/KoShapeMoveCommand.h b/libs/flake/commands/KoShapeMoveCommand.h index c619edc7dc..711222440a 100644 --- a/libs/flake/commands/KoShapeMoveCommand.h +++ b/libs/flake/commands/KoShapeMoveCommand.h @@ -1,62 +1,65 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2006 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPEMOVECOMMAND_H #define KOSHAPEMOVECOMMAND_H #include "kritaflake_export.h" #include #include #include #include class KoShape; /// The undo / redo command for shape moving. class KRITAFLAKE_EXPORT KoShapeMoveCommand : public KUndo2Command { public: /** * Constructor. * @param shapes the set of objects that are moved. * @param previousPositions the known set of previous positions for each of the objects. * this list naturally must have the same amount of items as the shapes set. * @param newPositions the new positions for the shapes. * this list naturally must have the same amount of items as the shapes set. * @param parent the parent command used for macro commands */ KoShapeMoveCommand(const QList &shapes, QList &previousPositions, QList &newPositions, KoFlake::AnchorPosition anchor = KoFlake::Center, KUndo2Command *parent = 0); + + KoShapeMoveCommand(const QList &shapes, const QPointF &offset, KUndo2Command *parent = 0); + ~KoShapeMoveCommand(); /// redo the command void redo() override; /// revert the actions done in redo void undo() override; int id() const override; bool mergeWith(const KUndo2Command *command) override; private: class Private; Private * const d; }; #endif diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp index e96d967720..970730f12f 100644 --- a/libs/flake/svg/SvgParser.cpp +++ b/libs/flake/svg/SvgParser.cpp @@ -1,1545 +1,1544 @@ /* This file is part of the KDE project * Copyright (C) 2002-2005,2007 Rob Buis * Copyright (C) 2002-2004 Nicolas Goutte * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005-2009 Jan Hambrecht * Copyright (C) 2005,2007 Thomas Zander * Copyright (C) 2006-2007 Inge Wallin * Copyright (C) 2007-2008,2010 Thorsten Zachmann * 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 "SvgParser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoFilterEffectStack.h" #include "KoFilterEffectLoadingContext.h" #include #include #include #include "SvgUtil.h" #include "SvgShape.h" #include "SvgGraphicContext.h" #include "SvgFilterHelper.h" #include "SvgGradientHelper.h" #include "SvgClipPathHelper.h" #include "parsers/SvgTransformParser.h" #include "kis_pointer_utils.h" #include #include #include "kis_debug.h" #include "kis_global.h" SvgParser::SvgParser(KoDocumentResourceManager *documentResourceManager) : m_context(documentResourceManager) , m_documentResourceManager(documentResourceManager) { } SvgParser::~SvgParser() { } void SvgParser::setXmlBaseDir(const QString &baseDir) { m_context.setInitialXmlBaseDir(baseDir); setFileFetcher( [this](const QString &name) { const QString fileName = m_context.xmlBaseDir() + QDir::separator() + name; QFile file(fileName); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(file.exists(), QByteArray()); file.open(QIODevice::ReadOnly); return file.readAll(); }); } void SvgParser::setResolution(const QRectF boundsInPixels, qreal pixelsPerInch) { KIS_ASSERT(!m_context.currentGC()); m_context.pushGraphicsContext(); m_context.currentGC()->pixelsPerInch = pixelsPerInch; const qreal scale = 72.0 / pixelsPerInch; const QTransform t = QTransform::fromScale(scale, scale); m_context.currentGC()->currentBoundingBox = boundsInPixels; m_context.currentGC()->matrix = t; } QList SvgParser::shapes() const { return m_shapes; } // Helper functions // --------------------------------------------------------------------------------------- SvgGradientHelper* SvgParser::findGradient(const QString &id) { SvgGradientHelper *result = 0; // check if gradient was already parsed, and return it if (m_gradients.contains(id)) { result = &m_gradients[ id ]; } // check if gradient was stored for later parsing if (!result && m_context.hasDefinition(id)) { const KoXmlElement &e = m_context.definition(id); if (e.tagName().contains("Gradient")) { result = parseGradient(m_context.definition(id)); } } return result; } QSharedPointer SvgParser::findPattern(const QString &id, const KoShape *shape) { QSharedPointer result; // check if gradient was stored for later parsing if (m_context.hasDefinition(id)) { const KoXmlElement &e = m_context.definition(id); if (e.tagName() == "pattern") { result = parsePattern(m_context.definition(id), shape); } } return result; } SvgFilterHelper* SvgParser::findFilter(const QString &id, const QString &href) { // check if filter was already parsed, and return it if (m_filters.contains(id)) return &m_filters[ id ]; // check if filter was stored for later parsing if (!m_context.hasDefinition(id)) return 0; const KoXmlElement &e = m_context.definition(id); if (e.childNodesCount() == 0) { QString mhref = e.attribute("xlink:href").mid(1); if (m_context.hasDefinition(mhref)) return findFilter(mhref, id); else return 0; } else { // ok parse filter now if (! parseFilter(m_context.definition(id), m_context.definition(href))) return 0; } // return successfully parsed filter or 0 QString n; if (href.isEmpty()) n = id; else n = href; if (m_filters.contains(n)) return &m_filters[ n ]; else return 0; } SvgClipPathHelper* SvgParser::findClipPath(const QString &id) { return m_clipPaths.contains(id) ? &m_clipPaths[id] : 0; } // Parsing functions // --------------------------------------------------------------------------------------- qreal SvgParser::parseUnit(const QString &unit, bool horiz, bool vert, const QRectF &bbox) { return SvgUtil::parseUnit(m_context.currentGC(), unit, horiz, vert, bbox); } qreal SvgParser::parseUnitX(const QString &unit) { return SvgUtil::parseUnitX(m_context.currentGC(), unit); } qreal SvgParser::parseUnitY(const QString &unit) { return SvgUtil::parseUnitY(m_context.currentGC(), unit); } qreal SvgParser::parseUnitXY(const QString &unit) { return SvgUtil::parseUnitXY(m_context.currentGC(), unit); } qreal SvgParser::parseAngular(const QString &unit) { return SvgUtil::parseUnitAngular(m_context.currentGC(), unit); } SvgGradientHelper* SvgParser::parseGradient(const KoXmlElement &e) { // IMPROVEMENTS: // - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again. // - A gradient inherits attributes it does not have from the referencing gradient. // - Gradients with no color stops have no fill or stroke. // - Gradients with one color stop have a solid color. SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return 0; SvgGradientHelper gradHelper; QString gradientId = e.attribute("id"); if (gradientId.isEmpty()) return 0; // check if we have this gradient already parsed // copy existing gradient if it exists if (m_gradients.contains(gradientId)) { return &m_gradients[gradientId]; } if (e.hasAttribute("xlink:href")) { // strip the '#' symbol QString href = e.attribute("xlink:href").mid(1); if (!href.isEmpty()) { // copy the referenced gradient if found SvgGradientHelper *pGrad = findGradient(href); if (pGrad) { gradHelper = *pGrad; } } } const QGradientStops defaultStops = gradHelper.gradient()->stops(); if (e.attribute("gradientUnits") == "userSpaceOnUse") { gradHelper.setGradientUnits(KoFlake::UserSpaceOnUse); } m_context.pushGraphicsContext(e); uploadStyleToContext(e); if (e.tagName() == "linearGradient") { QLinearGradient *g = new QLinearGradient(); if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStart(QPointF(SvgUtil::fromPercentage(e.attribute("x1", "0%")), SvgUtil::fromPercentage(e.attribute("y1", "0%")))); g->setFinalStop(QPointF(SvgUtil::fromPercentage(e.attribute("x2", "100%")), SvgUtil::fromPercentage(e.attribute("y2", "0%")))); } else { g->setStart(QPointF(parseUnitX(e.attribute("x1")), parseUnitY(e.attribute("y1")))); g->setFinalStop(QPointF(parseUnitX(e.attribute("x2")), parseUnitY(e.attribute("y2")))); } gradHelper.setGradient(g); } else if (e.tagName() == "radialGradient") { QRadialGradient *g = new QRadialGradient(); if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setCenter(QPointF(SvgUtil::fromPercentage(e.attribute("cx", "50%")), SvgUtil::fromPercentage(e.attribute("cy", "50%")))); g->setRadius(SvgUtil::fromPercentage(e.attribute("r", "50%"))); g->setFocalPoint(QPointF(SvgUtil::fromPercentage(e.attribute("fx", "50%")), SvgUtil::fromPercentage(e.attribute("fy", "50%")))); } else { g->setCenter(QPointF(parseUnitX(e.attribute("cx")), parseUnitY(e.attribute("cy")))); g->setFocalPoint(QPointF(parseUnitX(e.attribute("fx")), parseUnitY(e.attribute("fy")))); g->setRadius(parseUnitXY(e.attribute("r"))); } gradHelper.setGradient(g); } else { qDebug() << "WARNING: Failed to parse gradient with tag" << e.tagName(); } // handle spread method QGradient::Spread spreadMethod = QGradient::PadSpread; QString spreadMethodStr = e.attribute("spreadMethod"); if (!spreadMethodStr.isEmpty()) { if (spreadMethodStr == "reflect") { spreadMethod = QGradient::ReflectSpread; } else if (spreadMethodStr == "repeat") { spreadMethod = QGradient::RepeatSpread; } } gradHelper.setSpreadMode(spreadMethod); // Parse the color stops. m_context.styleParser().parseColorStops(gradHelper.gradient(), e, gc, defaultStops); if (e.hasAttribute("gradientTransform")) { SvgTransformParser p(e.attribute("gradientTransform")); if (p.isValid()) { gradHelper.setTransform(p.transform()); } } m_context.popGraphicsContext(); m_gradients.insert(gradientId, gradHelper); return &m_gradients[gradientId]; } inline QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset) { QTransform result = patternTransform * QTransform::fromTranslate(-shapeOffset.x(), -shapeOffset.y()) * patternTransform.inverted(); KIS_ASSERT_RECOVER_NOOP(result.type() <= QTransform::TxTranslate); return QPointF(result.dx(), result.dy()); } QSharedPointer SvgParser::parsePattern(const KoXmlElement &e, const KoShape *shape) { /** * Unlike the gradient parsing function, this method is called every time we * *reference* the pattern, not when we define it. Therefore we can already * use the coordinate system of the destination. */ QSharedPointer pattHelper; SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return pattHelper; const QString patternId = e.attribute("id"); if (patternId.isEmpty()) return pattHelper; pattHelper = toQShared(new KoVectorPatternBackground); if (e.hasAttribute("xlink:href")) { // strip the '#' symbol QString href = e.attribute("xlink:href").mid(1); if (!href.isEmpty() &&href != patternId) { // copy the referenced pattern if found QSharedPointer pPatt = findPattern(href, shape); if (pPatt) { pattHelper = pPatt; } } } pattHelper->setReferenceCoordinates( KoFlake::coordinatesFromString(e.attribute("patternUnits"), pattHelper->referenceCoordinates())); pattHelper->setContentCoordinates( KoFlake::coordinatesFromString(e.attribute("patternContentUnits"), pattHelper->contentCoordinates())); if (e.hasAttribute("patternTransform")) { SvgTransformParser p(e.attribute("patternTransform")); if (p.isValid()) { pattHelper->setPatternTransform(p.transform()); } } if (pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox) { QRectF referenceRect( SvgUtil::fromPercentage(e.attribute("x", "0%")), SvgUtil::fromPercentage(e.attribute("y", "0%")), SvgUtil::fromPercentage(e.attribute("width", "0%")), // 0% is according to SVG 1.1, don't ask me why! SvgUtil::fromPercentage(e.attribute("height", "0%"))); // 0% is according to SVG 1.1, don't ask me why! pattHelper->setReferenceRect(referenceRect); } else { QRectF referenceRect( parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")), parseUnitX(e.attribute("width", "0")), // 0 is according to SVG 1.1, don't ask me why! parseUnitY(e.attribute("height", "0"))); // 0 is according to SVG 1.1, don't ask me why! pattHelper->setReferenceRect(referenceRect); } /** * In Krita shapes X,Y coordinates are baked into the the shape global transform, but * the pattern should be painted in "user" coordinates. Therefore, we should handle * this offfset separately. * * TODO: Please also not that this offset is different from extraShapeOffset(), * because A.inverted() * B != A * B.inverted(). I'm not sure which variant is * correct (DK) */ const QTransform dstShapeTransform = shape->absoluteTransformation(0); const QTransform shapeOffsetTransform = dstShapeTransform * gc->matrix.inverted(); KIS_SAFE_ASSERT_RECOVER_NOOP(shapeOffsetTransform.type() <= QTransform::TxTranslate); const QPointF extraShapeOffset(shapeOffsetTransform.dx(), shapeOffsetTransform.dy()); m_context.pushGraphicsContext(e); gc = m_context.currentGC(); gc->workaroundClearInheritedFillProperties(); // HACK! // start building shape tree from scratch gc->matrix = QTransform(); const QRectF boundingRect = shape->outline().boundingRect()/*.translated(extraShapeOffset)*/; const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); // WARNING1: OBB and ViewBox transformations are *baked* into the pattern shapes! // although we expect the pattern be reusable, but it is not so! // WARNING2: the pattern shapes are stored in *User* coordinate system, although // the "official" content system might be either OBB or User. It means that // this baked transform should be stripped before writing the shapes back // into SVG if (e.hasAttribute("viewBox")) { gc->currentBoundingBox = pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox ? relativeToShape.mapRect(pattHelper->referenceRect()) : pattHelper->referenceRect(); applyViewBoxTransform(e); pattHelper->setContentCoordinates(pattHelper->referenceCoordinates()); } else if (pattHelper->contentCoordinates() == KoFlake::ObjectBoundingBox) { gc->matrix = relativeToShape * gc->matrix; } // We do *not* apply patternTransform here! Here we only bake the untransformed // version of the shape. The transformed one will be done in the very end while rendering. QList patternShapes = parseContainer(e); if (pattHelper->contentCoordinates() == KoFlake::UserSpaceOnUse) { // In Krita we normalize the shapes, bake this transform into the pattern shapes const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset); Q_FOREACH (KoShape *shape, patternShapes) { shape->applyAbsoluteTransformation(QTransform::fromTranslate(offset.x(), offset.y())); } } if (pattHelper->referenceCoordinates() == KoFlake::UserSpaceOnUse) { // In Krita we normalize the shapes, bake this transform into reference rect // NOTE: this is possible *only* when pattern transform is not perspective // (which is always true for SVG) const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset); QRectF ref = pattHelper->referenceRect(); ref.translate(offset); pattHelper->setReferenceRect(ref); } m_context.popGraphicsContext(); gc = m_context.currentGC(); if (!patternShapes.isEmpty()) { pattHelper->setShapes(patternShapes); } return pattHelper; } bool SvgParser::parseFilter(const KoXmlElement &e, const KoXmlElement &referencedBy) { SvgFilterHelper filter; // Use the filter that is referencing, or if there isn't one, the original filter KoXmlElement b; if (!referencedBy.isNull()) b = referencedBy; else b = e; // check if we are referencing another filter if (e.hasAttribute("xlink:href")) { QString href = e.attribute("xlink:href").mid(1); if (! href.isEmpty()) { // copy the referenced filter if found SvgFilterHelper *refFilter = findFilter(href); if (refFilter) filter = *refFilter; } } else { filter.setContent(b); } if (b.attribute("filterUnits") == "userSpaceOnUse") filter.setFilterUnits(KoFlake::UserSpaceOnUse); if (b.attribute("primitiveUnits") == "objectBoundingBox") filter.setPrimitiveUnits(KoFlake::ObjectBoundingBox); // parse filter region rectangle if (filter.filterUnits() == KoFlake::UserSpaceOnUse) { filter.setPosition(QPointF(parseUnitX(b.attribute("x")), parseUnitY(b.attribute("y")))); filter.setSize(QSizeF(parseUnitX(b.attribute("width")), parseUnitY(b.attribute("height")))); } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages filter.setPosition(QPointF(SvgUtil::fromPercentage(b.attribute("x", "-0.1")), SvgUtil::fromPercentage(b.attribute("y", "-0.1")))); filter.setSize(QSizeF(SvgUtil::fromPercentage(b.attribute("width", "1.2")), SvgUtil::fromPercentage(b.attribute("height", "1.2")))); } m_filters.insert(b.attribute("id"), filter); return true; } bool SvgParser::parseMarker(const KoXmlElement &e) { const QString id = e.attribute("id"); if (id.isEmpty()) return false; QScopedPointer marker(new KoMarker()); marker->setCoordinateSystem( KoMarker::coordinateSystemFromString(e.attribute("markerUnits", "strokeWidth"))); marker->setReferencePoint(QPointF(parseUnitX(e.attribute("refX")), parseUnitY(e.attribute("refY")))); marker->setReferenceSize(QSizeF(parseUnitX(e.attribute("markerWidth", "3")), parseUnitY(e.attribute("markerHeight", "3")))); const QString orientation = e.attribute("orient", "0"); if (orientation == "auto") { marker->setAutoOrientation(true); } else { marker->setExplicitOrientation(parseAngular(orientation)); } // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e, false); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->currentBoundingBox = QRectF(QPointF(0, 0), marker->referenceSize()); KoShape *markerShape = parseGroup(e); m_context.popGraphicsContext(); if (!markerShape) return false; marker->setShapes({markerShape}); m_markers.insert(id, QExplicitlySharedDataPointer(marker.take())); return true; } bool SvgParser::parseClipPath(const KoXmlElement &e) { SvgClipPathHelper clipPath; const QString id = e.attribute("id"); if (id.isEmpty()) return false; clipPath.setClipPathUnits( KoFlake::coordinatesFromString(e.attribute("clipPathUnits"), KoFlake::UserSpaceOnUse)); // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK! KoShape *clipShape = parseGroup(e); m_context.popGraphicsContext(); if (!clipShape) return false; clipPath.setShapes({clipShape}); m_clipPaths.insert(id, clipPath); return true; } bool SvgParser::parseClipMask(const KoXmlElement &e) { QSharedPointer clipMask(new KoClipMask); const QString id = e.attribute("id"); if (id.isEmpty()) return false; clipMask->setCoordinates(KoFlake::coordinatesFromString(e.attribute("maskUnits"), KoFlake::ObjectBoundingBox)); clipMask->setContentCoordinates(KoFlake::coordinatesFromString(e.attribute("maskContentUnits"), KoFlake::UserSpaceOnUse)); QRectF maskRect; if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) { maskRect.setRect( SvgUtil::fromPercentage(e.attribute("x", "-10%")), SvgUtil::fromPercentage(e.attribute("y", "-10%")), SvgUtil::fromPercentage(e.attribute("width", "120%")), SvgUtil::fromPercentage(e.attribute("height", "120%"))); } else { maskRect.setRect( parseUnitX(e.attribute("x", "-10%")), // yes, percents are insane in this case, parseUnitY(e.attribute("y", "-10%")), // but this is what SVG 1.1 tells us... parseUnitX(e.attribute("width", "120%")), parseUnitY(e.attribute("height", "120%"))); } clipMask->setMaskRect(maskRect); // ensure that the clip mask is loaded in local coordinates system m_context.pushGraphicsContext(e); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK! KoShape *clipShape = parseGroup(e); m_context.popGraphicsContext(); if (!clipShape) return false; clipMask->setShapes({clipShape}); m_clipMasks.insert(id, clipMask); return true; } void SvgParser::uploadStyleToContext(const KoXmlElement &e) { SvgStyles styles = m_context.styleParser().collectStyles(e); m_context.styleParser().parseFont(styles); m_context.styleParser().parseStyle(styles); } void SvgParser::applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { if (!shape) return; SvgGraphicsContext *gc = m_context.currentGC(); KIS_ASSERT(gc); if (!dynamic_cast(shape)) { applyFillStyle(shape); applyStrokeStyle(shape); } if (KoPathShape *pathShape = dynamic_cast(shape)) { applyMarkers(pathShape); } applyFilter(shape); applyClipping(shape, shapeToOriginalUserCoordinates); applyMaskClipping(shape, shapeToOriginalUserCoordinates); if (!gc->display || !gc->visible) { /** * WARNING: here is a small inconsistency with the standard: * in the standard, 'display' is not inherited, but in * flake it is! * * NOTE: though the standard says: "A value of 'display:none' indicates * that the given element and ***its children*** shall not be * rendered directly". Therefore, using setVisible(false) is fully * legitimate here (DK 29.11.16). */ shape->setVisible(false); } shape->setTransparency(1.0 - gc->opacity); } void SvgParser::applyStyle(KoShape *obj, const KoXmlElement &e, const QPointF &shapeToOriginalUserCoordinates) { applyStyle(obj, m_context.styleParser().collectStyles(e), shapeToOriginalUserCoordinates); } void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; m_context.styleParser().parseStyle(styles); if (!obj) return; if (!dynamic_cast(obj)) { applyFillStyle(obj); applyStrokeStyle(obj); } if (KoPathShape *pathShape = dynamic_cast(obj)) { applyMarkers(pathShape); } applyFilter(obj); applyClipping(obj, shapeToOriginalUserCoordinates); applyMaskClipping(obj, shapeToOriginalUserCoordinates); if (!gc->display || !gc->visible) { obj->setVisible(false); } obj->setTransparency(1.0 - gc->opacity); } QGradient* prepareGradientForShape(const SvgGradientHelper *gradient, const KoShape *shape, const SvgGraphicsContext *gc, QTransform *transform) { QGradient *resultGradient = 0; KIS_ASSERT(transform); if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) { resultGradient = KoFlake::cloneGradient(gradient->gradient()); *transform = gradient->transform(); } else { if (gradient->gradient()->type() == QGradient::LinearGradient) { /** * Create a converted gradient that looks the same, but linked to the * bounding rect of the shape, so it would be transformed with the shape */ const QRectF boundingRect = shape->outline().boundingRect(); const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); const QTransform relativeToUser = relativeToShape * shape->transformation() * gc->matrix.inverted(); const QTransform userToRelative = relativeToUser.inverted(); const QLinearGradient *o = static_cast(gradient->gradient()); QLinearGradient *g = new QLinearGradient(); g->setStart(userToRelative.map(o->start())); g->setFinalStop(userToRelative.map(o->finalStop())); g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStops(o->stops()); g->setSpread(o->spread()); resultGradient = g; *transform = relativeToUser * gradient->transform() * userToRelative; } else if (gradient->gradient()->type() == QGradient::RadialGradient) { // For radial and conical gradients such conversion is not possible resultGradient = KoFlake::cloneGradient(gradient->gradient()); *transform = gradient->transform() * gc->matrix * shape->transformation().inverted(); } } return resultGradient; } void SvgParser::applyFillStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->fillType == SvgGraphicsContext::None) { shape->setBackground(QSharedPointer(0)); } else if (gc->fillType == SvgGraphicsContext::Solid) { shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } else if (gc->fillType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->fillId); if (gradient) { QTransform transform; QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform); if (result) { QSharedPointer bg; bg = toQShared(new KoGradientBackground(result)); bg->setTransform(transform); shape->setBackground(bg); } } else { QSharedPointer pattern = findPattern(gc->fillId, shape); if (pattern) { shape->setBackground(pattern); } else { // no referenced fill found, use fallback color shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } } } KoPathShape *path = dynamic_cast(shape); if (path) path->setFillRule(gc->fillRule); } void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke) { const double lineWidth = srcStroke->lineWidth(); QVector dashes = srcStroke->lineDashes(); // apply line width to dashes and dash offset if (dashes.count() && lineWidth > 0.0) { const double dashOffset = srcStroke->dashOffset(); QVector dashes = srcStroke->lineDashes(); for (int i = 0; i < dashes.count(); ++i) { dashes[i] /= lineWidth; } dstStroke->setLineStyle(Qt::CustomDashLine, dashes); dstStroke->setDashOffset(dashOffset / lineWidth); } else { dstStroke->setLineStyle(Qt::SolidLine, QVector()); } } void SvgParser::applyStrokeStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->strokeType == SvgGraphicsContext::None) { shape->setStroke(KoShapeStrokeModelSP()); } else if (gc->strokeType == SvgGraphicsContext::Solid) { KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } else if (gc->strokeType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->strokeId); if (gradient) { QTransform transform; QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform); if (result) { QBrush brush = *result; delete result; brush.setTransform(transform); KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); stroke->setLineBrush(brush); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } } else { // no referenced stroke found, use fallback color KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } } } void SvgParser::applyFilter(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->filterId.isEmpty()) return; SvgFilterHelper *filter = findFilter(gc->filterId); if (! filter) return; KoXmlElement content = filter->content(); // parse filter region QRectF bound(shape->position(), shape->size()); // work on bounding box without viewbox tranformation applied // so user space coordinates of bounding box and filter region match up bound = gc->viewboxTransform.inverted().mapRect(bound); QRectF filterRegion(filter->position(bound), filter->size(bound)); // convert filter region to boundingbox units QRectF objectFilterRegion; objectFilterRegion.setTopLeft(SvgUtil::userSpaceToObject(filterRegion.topLeft(), bound)); objectFilterRegion.setSize(SvgUtil::userSpaceToObject(filterRegion.size(), bound)); KoFilterEffectLoadingContext context(m_context.xmlBaseDir()); context.setShapeBoundingBox(bound); // enable units conversion context.enableFilterUnitsConversion(filter->filterUnits() == KoFlake::UserSpaceOnUse); context.enableFilterPrimitiveUnitsConversion(filter->primitiveUnits() == KoFlake::UserSpaceOnUse); KoFilterEffectRegistry *registry = KoFilterEffectRegistry::instance(); KoFilterEffectStack *filterStack = 0; QSet stdInputs; stdInputs << "SourceGraphic" << "SourceAlpha"; stdInputs << "BackgroundImage" << "BackgroundAlpha"; stdInputs << "FillPaint" << "StrokePaint"; QMap inputs; // create the filter effects and add them to the shape for (KoXmlNode n = content.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement primitive = n.toElement(); KoFilterEffect *filterEffect = registry->createFilterEffectFromXml(primitive, context); if (!filterEffect) { debugFlake << "filter effect" << primitive.tagName() << "is not implemented yet"; continue; } const QString input = primitive.attribute("in"); if (!input.isEmpty()) { filterEffect->setInput(0, input); } const QString output = primitive.attribute("result"); if (!output.isEmpty()) { filterEffect->setOutput(output); } QRectF subRegion; // parse subregion if (filter->primitiveUnits() == KoFlake::UserSpaceOnUse) { const QString xa = primitive.attribute("x"); const QString ya = primitive.attribute("y"); const QString wa = primitive.attribute("width"); const QString ha = primitive.attribute("height"); if (xa.isEmpty() || ya.isEmpty() || wa.isEmpty() || ha.isEmpty()) { bool hasStdInput = false; bool isFirstEffect = filterStack == 0; // check if one of the inputs is a standard input Q_FOREACH (const QString &input, filterEffect->inputs()) { if ((isFirstEffect && input.isEmpty()) || stdInputs.contains(input)) { hasStdInput = true; break; } } if (hasStdInput || primitive.tagName() == "feImage") { // default to 0%, 0%, 100%, 100% subRegion.setTopLeft(QPointF(0, 0)); subRegion.setSize(QSizeF(1, 1)); } else { // defaults to bounding rect of all referenced nodes Q_FOREACH (const QString &input, filterEffect->inputs()) { if (!inputs.contains(input)) continue; KoFilterEffect *inputFilter = inputs[input]; if (inputFilter) subRegion |= inputFilter->filterRect(); } } } else { const qreal x = parseUnitX(xa); const qreal y = parseUnitY(ya); const qreal w = parseUnitX(wa); const qreal h = parseUnitY(ha); subRegion.setTopLeft(SvgUtil::userSpaceToObject(QPointF(x, y), bound)); subRegion.setSize(SvgUtil::userSpaceToObject(QSizeF(w, h), bound)); } } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages const qreal x = SvgUtil::fromPercentage(primitive.attribute("x", "0")); const qreal y = SvgUtil::fromPercentage(primitive.attribute("y", "0")); const qreal w = SvgUtil::fromPercentage(primitive.attribute("width", "1")); const qreal h = SvgUtil::fromPercentage(primitive.attribute("height", "1")); subRegion = QRectF(QPointF(x, y), QSizeF(w, h)); } filterEffect->setFilterRect(subRegion); if (!filterStack) filterStack = new KoFilterEffectStack(); filterStack->appendFilterEffect(filterEffect); inputs[filterEffect->output()] = filterEffect; } if (filterStack) { filterStack->setClipRect(objectFilterRegion); shape->setFilterEffectStack(filterStack); } } void SvgParser::applyMarkers(KoPathShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; if (!gc->markerStartId.isEmpty() && m_markers.contains(gc->markerStartId)) { shape->setMarker(m_markers[gc->markerStartId].data(), KoFlake::StartMarker); } if (!gc->markerMidId.isEmpty() && m_markers.contains(gc->markerMidId)) { shape->setMarker(m_markers[gc->markerMidId].data(), KoFlake::MidMarker); } if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) { shape->setMarker(m_markers[gc->markerEndId].data(), KoFlake::EndMarker); } shape->setAutoFillMarkers(gc->autoFillMarkers); } void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->clipPathId.isEmpty()) return; SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId); if (!clipPath || clipPath->isEmpty()) return; QList shapes; Q_FOREACH (KoShape *item, clipPath->shapes()) { KoShape *clonedShape = item->cloneShape(); KIS_ASSERT_RECOVER(clonedShape) { continue; } shapes.append(clonedShape); } if (!shapeToOriginalUserCoordinates.isNull()) { const QTransform t = QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(), shapeToOriginalUserCoordinates.y()); Q_FOREACH(KoShape *s, shapes) { s->applyAbsoluteTransformation(t); } } KoClipPath *clipPathObject = new KoClipPath(shapes, clipPath->clipPathUnits() == KoFlake::ObjectBoundingBox ? KoFlake::ObjectBoundingBox : KoFlake::UserSpaceOnUse); shape->setClipPath(clipPathObject); } void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; if (gc->clipMaskId.isEmpty()) return; QSharedPointer originalClipMask = m_clipMasks.value(gc->clipMaskId); if (!originalClipMask || originalClipMask->isEmpty()) return; KoClipMask *clipMask = originalClipMask->clone(); clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates); shape->setClipMask(clipMask); } KoShape* SvgParser::parseUse(const KoXmlElement &e) { KoShape *result = 0; QString id = e.attribute("xlink:href"); if (!id.isEmpty()) { QString key = id.mid(1); if (m_context.hasDefinition(key)) { SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); // TODO: parse 'width' and 'hight' as well gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0"))); const KoXmlElement &referencedElement = m_context.definition(key); result = parseGroup(e, referencedElement); m_context.popGraphicsContext(); } } return result; } void SvgParser::addToGroup(QList shapes, KoShapeGroup *group) { m_shapes += shapes; if (!group || shapes.isEmpty()) return; // not clipped, but inherit transform KoShapeGroupCommand cmd(group, shapes, false, true, false); cmd.redo(); } QList SvgParser::parseSvg(const KoXmlElement &e, QSizeF *fragmentSize) { // check if we are the root svg element const bool isRootSvg = m_context.isRootContext(); // parse 'transform' field if preset SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); applyStyle(0, e, QPointF()); const QString w = e.attribute("width"); const QString h = e.attribute("height"); const qreal width = w.isEmpty() ? 666.0 : parseUnitX(w); const qreal height = h.isEmpty() ? 555.0 : parseUnitY(h); QSizeF svgFragmentSize(QSizeF(width, height)); if (fragmentSize) { *fragmentSize = svgFragmentSize; } gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize); if (!isRootSvg) { // x and y attribute has no meaning for outermost svg elements const qreal x = parseUnit(e.attribute("x", "0")); const qreal y = parseUnit(e.attribute("y", "0")); QTransform move = QTransform::fromTranslate(x, y); gc->matrix = move * gc->matrix; } applyViewBoxTransform(e); QList shapes; // SVG 1.1: skip the rendering of the element if it has null viewBox if (gc->currentBoundingBox.isValid()) { shapes = parseContainer(e); } m_context.popGraphicsContext(); return shapes; } void SvgParser::applyViewBoxTransform(const KoXmlElement &element) { SvgGraphicsContext *gc = m_context.currentGC(); QRectF viewRect = gc->currentBoundingBox; QTransform viewTransform; if (SvgUtil::parseViewBox(gc, element, gc->currentBoundingBox, &viewRect, &viewTransform)) { gc->matrix = viewTransform * gc->matrix; gc->currentBoundingBox = viewRect; } } QList > SvgParser::knownMarkers() const { return m_markers.values(); } void SvgParser::setFileFetcher(SvgParser::FileFetcherFunc func) { m_context.setFileFetcher(func); } inline QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading) { const QTransform shapeToOriginalUserCoordinates = shape->absoluteTransformation(0).inverted() * coordinateSystemOnLoading; KIS_SAFE_ASSERT_RECOVER_NOOP(shapeToOriginalUserCoordinates.type() <= QTransform::TxTranslate); return QPointF(shapeToOriginalUserCoordinates.dx(), shapeToOriginalUserCoordinates.dy()); } KoShape* SvgParser::parseGroup(const KoXmlElement &b, const KoXmlElement &overrideChildrenFrom) { m_context.pushGraphicsContext(b); KoShapeGroup *group = new KoShapeGroup(); group->setZIndex(m_context.nextZIndex()); // groups should also have their own coordinate system! group->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(group, m_context.currentGC()->matrix); uploadStyleToContext(b); QList childShapes; if (!overrideChildrenFrom.isNull()) { // we upload styles from both: and uploadStyleToContext(overrideChildrenFrom); childShapes = parseSingleElement(overrideChildrenFrom); } else { childShapes = parseContainer(b); } // handle id applyId(b.attribute("id"), group); addToGroup(childShapes, group); applyCurrentStyle(group, extraOffset); // apply style to this group after size is set m_context.popGraphicsContext(); return group; } QList SvgParser::parseContainer(const KoXmlElement &e) { QList shapes; // are we parsing a switch container bool isSwitch = e.tagName() == "switch"; for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement b = n.toElement(); if (b.isNull()) continue; if (isSwitch) { // if we are parsing a switch check the requiredFeatures, requiredExtensions // and systemLanguage attributes // TODO: evaluate feature list if (b.hasAttribute("requiredFeatures")) { continue; } if (b.hasAttribute("requiredExtensions")) { // we do not support any extensions continue; } if (b.hasAttribute("systemLanguage")) { // not implemeted yet } } QList currentShapes = parseSingleElement(b); shapes.append(currentShapes); // if we are parsing a switch, stop after the first supported element if (isSwitch && !currentShapes.isEmpty()) break; } return shapes; } QList SvgParser::parseSingleElement(const KoXmlElement &b) { QList shapes; // save definition for later instantiation with 'use' m_context.addDefinition(b); if (b.tagName() == "svg") { shapes += parseSvg(b); } else if (b.tagName() == "g" || b.tagName() == "a" || b.tagName() == "symbol") { // treat svg link as group so we don't miss its child elements shapes += parseGroup(b); } else if (b.tagName() == "switch") { m_context.pushGraphicsContext(b); shapes += parseContainer(b); m_context.popGraphicsContext(); } else if (b.tagName() == "defs") { if (b.childNodesCount() > 0) { /** * WARNING: 'defs' are basically 'display:none' style, therefore they should not play * any role in shapes outline calculation. But setVisible(false) shapes do! * Should be fixed in the future! */ KoShape *defsShape = parseGroup(b); defsShape->setVisible(false); - shapes += defsShape; } } else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") { } else if (b.tagName() == "pattern") { } else if (b.tagName() == "filter") { parseFilter(b); } else if (b.tagName() == "clipPath") { parseClipPath(b); } else if (b.tagName() == "mask") { parseClipMask(b); } else if (b.tagName() == "marker") { parseMarker(b); } else if (b.tagName() == "style") { m_context.addStyleSheet(b); } else if (b.tagName() == "rect" || b.tagName() == "ellipse" || b.tagName() == "circle" || b.tagName() == "line" || b.tagName() == "polyline" || b.tagName() == "polygon" || b.tagName() == "path" || b.tagName() == "image" || b.tagName() == "text") { KoShape *shape = createObjectDirect(b); if (shape) shapes.append(shape); } else if (b.tagName() == "use") { shapes += parseUse(b); } else if (b.tagName() == "color-profile") { m_context.parseProfile(b); } else { // this is an unknown element, so try to load it anyway // there might be a shape that handles that element KoShape *shape = createObject(b); if (shape) { shapes.append(shape); } } return shapes; } // Creating functions // --------------------------------------------------------------------------------------- KoShape * SvgParser::createPath(const KoXmlElement &element) { KoShape *obj = 0; if (element.tagName() == "line") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { double x1 = element.attribute("x1").isEmpty() ? 0.0 : parseUnitX(element.attribute("x1")); double y1 = element.attribute("y1").isEmpty() ? 0.0 : parseUnitY(element.attribute("y1")); double x2 = element.attribute("x2").isEmpty() ? 0.0 : parseUnitX(element.attribute("x2")); double y2 = element.attribute("y2").isEmpty() ? 0.0 : parseUnitY(element.attribute("y2")); path->clear(); path->moveTo(QPointF(x1, y1)); path->lineTo(QPointF(x2, y2)); path->normalize(); obj = path; } } else if (element.tagName() == "polyline" || element.tagName() == "polygon") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { path->clear(); bool bFirst = true; QString points = element.attribute("points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); QStringList pointList = points.split(' ', QString::SkipEmptyParts); for (QStringList::Iterator it = pointList.begin(); it != pointList.end(); ++it) { QPointF point; point.setX(SvgUtil::fromUserSpace((*it).toDouble())); ++it; if (it == pointList.end()) break; point.setY(SvgUtil::fromUserSpace((*it).toDouble())); if (bFirst) { path->moveTo(point); bFirst = false; } else path->lineTo(point); } if (element.tagName() == "polygon") path->close(); path->setPosition(path->normalize()); obj = path; } } else if (element.tagName() == "path") { KoPathShape *path = static_cast(createShape(KoPathShapeId)); if (path) { path->clear(); KoPathShapeLoader loader(path); loader.parseSvg(element.attribute("d"), true); path->setPosition(path->normalize()); QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()), SvgUtil::fromUserSpace(path->position().y())); QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()), SvgUtil::fromUserSpace(path->size().height())); path->setSize(newSize); path->setPosition(newPosition); obj = path; } } return obj; } KoShape * SvgParser::createObjectDirect(const KoXmlElement &b) { m_context.pushGraphicsContext(b); uploadStyleToContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix); applyCurrentStyle(obj, extraOffset); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createObject(const KoXmlElement &b, const SvgStyles &style) { m_context.pushGraphicsContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix); SvgStyles objStyle = style.isEmpty() ? m_context.styleParser().collectStyles(b) : style; m_context.styleParser().parseFont(objStyle); applyStyle(obj, objStyle, extraOffset); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createShapeFromElement(const KoXmlElement &element, SvgLoadingContext &context) { KoShape *object = 0; const QString tagName = SvgUtil::mapExtendedShapeTag(element.tagName(), element); QList factories = KoShapeRegistry::instance()->factoriesForElement(KoXmlNS::svg, tagName); foreach (KoShapeFactoryBase *f, factories) { KoShape *shape = f->createDefaultShape(m_documentResourceManager); if (!shape) continue; SvgShape *svgShape = dynamic_cast(shape); if (!svgShape) { delete shape; continue; } // reset transformation that might come from the default shape shape->setTransformation(QTransform()); // reset border KoShapeStrokeModelSP oldStroke = shape->stroke(); shape->setStroke(KoShapeStrokeModelSP()); // reset fill shape->setBackground(QSharedPointer(0)); if (!svgShape->loadSvg(element, context)) { delete shape; continue; } object = shape; break; } if (!object) { object = createPath(element); } return object; } KoShape * SvgParser::createShape(const QString &shapeID) { KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(shapeID); if (!factory) { debugFlake << "Could not find factory for shape id" << shapeID; return 0; } KoShape *shape = factory->createDefaultShape(m_documentResourceManager); if (!shape) { debugFlake << "Could not create Default shape for shape id" << shapeID; return 0; } if (shape->shapeId().isEmpty()) shape->setShapeId(factory->id()); // reset tranformation that might come from the default shape shape->setTransformation(QTransform()); // reset border KoShapeStrokeModelSP oldStroke = shape->stroke(); shape->setStroke(KoShapeStrokeModelSP()); // reset fill shape->setBackground(QSharedPointer(0)); return shape; } void SvgParser::applyId(const QString &id, KoShape *shape) { if (id.isEmpty()) return; shape->setName(id); m_context.registerShape(id, shape); } diff --git a/libs/flake/tests/CMakeLists.txt b/libs/flake/tests/CMakeLists.txt index 0a4abe4513..edb3516a4f 100644 --- a/libs/flake/tests/CMakeLists.txt +++ b/libs/flake/tests/CMakeLists.txt @@ -1,88 +1,94 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include(ECMAddTests) include(KritaAddBrokenUnitTest) macro_add_unittest_definitions() ecm_add_tests( TestPosition.cpp TestSelection.cpp TestPathTool.cpp TestShapeAt.cpp TestShapePainting.cpp TestKoShapeFactory.cpp TestKoShapeRegistry.cpp TestShapeContainer.cpp TestShapeGroupCommand.cpp TestShapeReorderCommand.cpp TestImageCollection.cpp TestResourceManager.cpp TestShapeBackgroundCommand.cpp TestShapeStrokeCommand.cpp TestShapeShadowCommand.cpp TestInputDevice.cpp TestSnapStrategy.cpp NAME_PREFIX "libs-kritaflake-" LINK_LIBRARIES kritaflake Qt5::Test) krita_add_broken_unit_test(TestPathShape.cpp TEST_NAME libs-kritaflake-TestPathShape LINK_LIBRARIES kritaflake Qt5::Test) krita_add_broken_unit_test(TestControlPointMoveCommand.cpp TEST_NAME libs-kritaflake-TestControlPointMoveCommand LINK_LIBRARIES kritaflake Qt5::Test) krita_add_broken_unit_test(TestPointTypeCommand.cpp TEST_NAME libs-kritaflake-TestPointTypeCommand LINK_LIBRARIES kritaflake Qt5::Test) krita_add_broken_unit_test(TestPointRemoveCommand.cpp TEST_NAME libs-kritaflake-TestPointRemoveCommand LINK_LIBRARIES kritaflake Qt5::Test) krita_add_broken_unit_test(TestRemoveSubpathCommand.cpp TEST_NAME libs-kritaflake-TestRemoveSubpathCommand LINK_LIBRARIES kritaflake Qt5::Test) krita_add_broken_unit_test(TestPathSegment.cpp TEST_NAME libs-kritaflake-TestPathSegment LINK_LIBRARIES kritaflake Qt5::Test) krita_add_broken_unit_test(TestSegmentTypeCommand.cpp TEST_NAME libs-kritaflake-TestSegmentTypeCommand LINK_LIBRARIES kritaflake Qt5::Test) krita_add_broken_unit_test(TestPointMergeCommand.cpp TEST_NAME libs-kritaflake-TestPointMergeCommand LINK_LIBRARIES kritaflake Qt5::Test) +ecm_add_test( + TestKoDrag.cpp + TEST_NAME libs-kritaflake-TestKoDrag + LINK_LIBRARIES kritaflake Qt5::Test +) + ecm_add_test( TestKoMarkerCollection.cpp TEST_NAME libs-kritaflake-TestKoMarkerCollection LINK_LIBRARIES kritaflake Qt5::Test ) ecm_add_test( TestSvgParser.cpp TEST_NAME libs-kritaflake-TestSvgParser LINK_LIBRARIES kritaflake Qt5::Test ) ecm_add_test( TestSvgParser.cpp TEST_NAME libs-kritaflake-TestSvgParserCloned LINK_LIBRARIES kritaflake Qt5::Test ) set_property(TARGET libs-kritaflake-TestSvgParserCloned PROPERTY COMPILE_DEFINITIONS USE_CLONED_SHAPES) ecm_add_test( TestSvgParser.cpp TEST_NAME libs-kritaflake-TestSvgParserRoundTrip LINK_LIBRARIES kritaflake Qt5::Test ) set_property(TARGET libs-kritaflake-TestSvgParserRoundTrip PROPERTY COMPILE_DEFINITIONS USE_ROUND_TRIP) diff --git a/libs/flake/tests/MockShapes.h b/libs/flake/tests/MockShapes.h index 829791931a..07d9cec637 100644 --- a/libs/flake/tests/MockShapes.h +++ b/libs/flake/tests/MockShapes.h @@ -1,241 +1,249 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * 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 MOCKSHAPES_H #define MOCKSHAPES_H #include #include #include #include #include #include #include "KoShapeManager.h" #include "FlakeDebug.h" #include "KoSnapData.h" #include "KoUnit.h" class MockShape : public KoShape { public: MockShape() : paintedCount(0) {} void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); //qDebug() << "Shape" << kBacktrace( 10 ); paintedCount++; } virtual void saveOdf(KoShapeSavingContext &) const {} virtual bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) { return true; } int paintedCount; }; class MockContainer : public KoShapeContainer { public: MockContainer(KoShapeContainerModel *model = 0) : KoShapeContainer(model), paintedCount(0) {} void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); //qDebug() << "Container:" << kBacktrace( 10 ); paintedCount++; } virtual void saveOdf(KoShapeSavingContext &) const {} virtual bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) { return true; } int paintedCount; }; class MockGroup : public KoShapeGroup { void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } }; class KoToolProxy; class MockShapeController : public KoShapeBasedDocumentBase { public: void addShape(KoShape* shape) { m_shapes.insert(shape); if (m_shapeManager) { m_shapeManager->addShape(shape); } } void removeShape(KoShape* shape) { m_shapes.remove(shape); if (m_shapeManager) { m_shapeManager->remove(shape); } } bool contains(KoShape* shape) { return m_shapes.contains(shape); } void setShapeManager(KoShapeManager *shapeManager) { m_shapeManager = shapeManager; } + QRectF documentRectInPixels() const { + return QRectF(0,0,100,100); + } + + qreal pixelsPerInch() const { + return 72.0; + } + private: QSet m_shapes; KoShapeManager *m_shapeManager = 0; }; class MockCanvas : public KoCanvasBase { public: MockCanvas(KoShapeBasedDocumentBase *aKoShapeBasedDocumentBase =0)//made for TestSnapStrategy.cpp : KoCanvasBase(aKoShapeBasedDocumentBase), m_shapeManager(new KoShapeManager(this)), m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data())) { if (MockShapeController *controller = dynamic_cast(aKoShapeBasedDocumentBase)) { controller->setShapeManager(m_shapeManager.data()); } } ~MockCanvas() {} void setHorz(qreal pHorz){ m_horz = pHorz; } void setVert(qreal pVert){ m_vert = pVert; } void gridSize(QPointF *offset, QSizeF *spacing) const { Q_UNUSED(offset); spacing->setWidth(m_horz); spacing->setHeight(m_vert); } bool snapToGrid() const { return true; } void addCommand(KUndo2Command*) { } KoShapeManager *shapeManager() const { return m_shapeManager.data(); } KoSelectedShapesProxy *selectedShapesProxy() const { return m_selectedShapesProxy.data(); } void updateCanvas(const QRectF&) {} KoToolProxy * toolProxy() const { return 0; } KoViewConverter *viewConverter() const { return 0; } QWidget* canvasWidget() { return 0; } const QWidget* canvasWidget() const { return 0; } KoUnit unit() const { return KoUnit(KoUnit::Millimeter); } void updateInputMethodInfo() {} void setCursor(const QCursor &) {} private: QScopedPointer m_shapeManager; QScopedPointer m_selectedShapesProxy; qreal m_horz; qreal m_vert; }; class MockContainerModel : public KoShapeContainerModel { public: MockContainerModel() { resetCounts(); } /// reimplemented void add(KoShape *child) { m_children.append(child); // note that we explicitly do not check for duplicates here! } /// reimplemented void remove(KoShape *child) { m_children.removeAll(child); } /// reimplemented void setClipped(const KoShape *, bool) { } // ignored /// reimplemented bool isClipped(const KoShape *) const { return false; }// ignored /// reimplemented bool isChildLocked(const KoShape *child) const { return child->isGeometryProtected(); } /// reimplemented int count() const { return m_children.count(); } /// reimplemented QList shapes() const { return m_children; } /// reimplemented void containerChanged(KoShapeContainer *, KoShape::ChangeType) { m_containerChangedCalled++; } /// reimplemented void proposeMove(KoShape *, QPointF &) { m_proposeMoveCalled++; } /// reimplemented void childChanged(KoShape *, KoShape::ChangeType) { m_childChangedCalled++; } void setInheritsTransform(const KoShape *, bool) { } bool inheritsTransform(const KoShape *) const { return false; } int containerChangedCalled() const { return m_containerChangedCalled; } int childChangedCalled() const { return m_childChangedCalled; } int proposeMoveCalled() const { return m_proposeMoveCalled; } void resetCounts() { m_containerChangedCalled = 0; m_childChangedCalled = 0; m_proposeMoveCalled = 0; } private: QList m_children; int m_containerChangedCalled, m_childChangedCalled, m_proposeMoveCalled; }; #endif diff --git a/libs/flake/tests/TestKoDrag.cpp b/libs/flake/tests/TestKoDrag.cpp new file mode 100644 index 0000000000..543f40bf83 --- /dev/null +++ b/libs/flake/tests/TestKoDrag.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2017 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 "TestKoDrag.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "../../sdk/tests/qimage_test_util.h" + +void TestKoDrag::test() +{ + const QString fileName = TestUtil::fetchDataFileLazy("test_svg_file.svg"); + QVERIFY(!fileName.isEmpty()); + + QFile testShapes(fileName); + testShapes.open(QIODevice::ReadOnly); + + KoXmlDocument doc; + doc.setContent(testShapes.readAll()); + + KoDocumentResourceManager resourceManager; + SvgParser parser(&resourceManager); + parser.setResolution(QRectF(0, 0, 30, 30) /* px */, 72 /* ppi */); + + QSizeF fragmentSize; + QList shapes = parser.parseSvg(doc.documentElement(), &fragmentSize); + QCOMPARE(fragmentSize, QSizeF(30,30)); + + { + QCOMPARE(shapes.size(), 1); + + KoShapeGroup *layer = dynamic_cast(shapes.first()); + QVERIFY(layer); + QCOMPARE(layer->shapeCount(), 2); + + QCOMPARE(KoShape::boundingRect(shapes).toAlignedRect(), QRect(4,3,24,24)); + } + + KoDrag drag; + drag.setSvg(shapes); + drag.addToClipboard(); + + KoSvgPaste paste; + QVERIFY(paste.hasShapes()); + + QList newShapes = paste.fetchShapes(QRectF(0,0,15,15) /* px */, 144 /* ppi */, &fragmentSize); + + { + QCOMPARE(newShapes.size(), 1); + + KoShapeGroup *layer = dynamic_cast(newShapes.first()); + QVERIFY(layer); + QCOMPARE(layer->shapeCount(), 2); + + QCOMPARE(fragmentSize.toSize(), QSize(54, 53)); + QCOMPARE(KoShape::boundingRect(newShapes).toAlignedRect(), QRect(4,3,24,24)); + } + + + qDeleteAll(shapes); + qDeleteAll(newShapes); +} + + +QTEST_MAIN(TestKoDrag) diff --git a/libs/flake/tests/TestKoDrag.h b/libs/flake/tests/TestKoDrag.h new file mode 100644 index 0000000000..edb6a58083 --- /dev/null +++ b/libs/flake/tests/TestKoDrag.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2017 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 TESTKODRAG_H +#define TESTKODRAG_H + +#include +#include + +class TestKoDrag : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void test(); +}; + +#endif // TESTKODRAG_H diff --git a/libs/flake/tests/data/test_svg_file.svg b/libs/flake/tests/data/test_svg_file.svg new file mode 100644 index 0000000000..025850d432 --- /dev/null +++ b/libs/flake/tests/data/test_svg_file.svg @@ -0,0 +1,76 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/libs/global/kis_algebra_2d.h b/libs/global/kis_algebra_2d.h index a64f7874c4..d001586026 100644 --- a/libs/global/kis_algebra_2d.h +++ b/libs/global/kis_algebra_2d.h @@ -1,548 +1,564 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_ALGEBRA_2D_H #define __KIS_ALGEBRA_2D_H #include #include #include #include #include #include #include #include #include class QPainterPath; class QTransform; namespace KisAlgebra2D { template struct PointTypeTraits { }; template <> struct PointTypeTraits { typedef int value_type; typedef qreal calculation_type; typedef QRect rect_type; }; template <> struct PointTypeTraits { typedef qreal value_type; typedef qreal calculation_type; typedef QRectF rect_type; }; template typename PointTypeTraits::value_type dotProduct(const T &a, const T &b) { return a.x() * b.x() + a.y() * b.y(); } template typename PointTypeTraits::value_type crossProduct(const T &a, const T &b) { return a.x() * b.y() - a.y() * b.x(); } template qreal norm(const T &a) { return std::sqrt(pow2(a.x()) + pow2(a.y())); } template Point normalize(const Point &a) { const qreal length = norm(a); return (1.0 / length) * a; } /** * Usual sign() function with positive zero */ template T signPZ(T x) { return x >= T(0) ? T(1) : T(-1); } /** * Usual sign() function with zero returning zero */ template T signZZ(T x) { return x == T(0) ? T(0) : x > T(0) ? T(1) : T(-1); } /** * Copies the sign of \p y into \p x and returns the result */ template inline T copysign(T x, T y) { T strippedX = qAbs(x); return y >= T(0) ? strippedX : -strippedX; } template T leftUnitNormal(const T &a) { T result = a.x() != 0 ? T(-a.y() / a.x(), 1) : T(-1, 0); qreal length = norm(result); result *= (crossProduct(a, result) >= 0 ? 1 : -1) / length; return -result; } template T rightUnitNormal(const T &a) { return -leftUnitNormal(a); } template T inwardUnitNormal(const T &a, int polygonDirection) { return polygonDirection * leftUnitNormal(a); } /** * \return 1 if the polygon is counterclockwise * -1 if the polygon is clockwise * * Note: the sign is flipped because our 0y axis * is reversed */ template int polygonDirection(const QVector &polygon) { typename PointTypeTraits::value_type doubleSum = 0; const int numPoints = polygon.size(); for (int i = 1; i <= numPoints; i++) { int prev = i - 1; int next = i == numPoints ? 0 : i; doubleSum += (polygon[next].x() - polygon[prev].x()) * (polygon[next].y() + polygon[prev].y()); } return doubleSum >= 0 ? 1 : -1; } template bool isInRange(T x, T a, T b) { T length = qAbs(a - b); return qAbs(x - a) <= length && qAbs(x - b) <= length; } void KRITAGLOBAL_EXPORT adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt); /** * Let \p pt, \p base1 are two vectors. \p base1 is uniformly scaled * and then rotated into \p base2 using transformation matrix S * * R. The function applies the same transformation to \pt and returns * the result. **/ QPointF KRITAGLOBAL_EXPORT transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2); qreal KRITAGLOBAL_EXPORT angleBetweenVectors(const QPointF &v1, const QPointF &v2); namespace Private { inline void resetEmptyRectangle(const QPoint &pt, QRect *rc) { *rc = QRect(pt, QSize(1, 1)); } inline void resetEmptyRectangle(const QPointF &pt, QRectF *rc) { static const qreal eps = 1e-10; *rc = QRectF(pt, QSizeF(eps, eps)); } } template inline void accumulateBounds(const Point &pt, Rect *bounds) { if (bounds->isEmpty()) { Private::resetEmptyRectangle(pt, bounds); } if (pt.x() > bounds->right()) { bounds->setRight(pt.x()); } if (pt.x() < bounds->left()) { bounds->setLeft(pt.x()); } if (pt.y() > bounds->bottom()) { bounds->setBottom(pt.y()); } if (pt.y() < bounds->top()) { bounds->setTop(pt.y()); } } template