diff --git a/krita/krita.action b/krita/krita.action index 7f5c5ad209..6b622f150c 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -1,3590 +1,3650 @@ General Open Resources Folder Opens a file browser at the location Krita saves resources such as brushes to. Opens a file browser at the location Krita saves resources such as brushes to. Open Resources Folder 0 0 false C&ascade Cascade Cascade 10 0 false &Tile Tile Tile 10 0 false Create Resource Bundle... Create Resource Bundle Create Resource Bundle 0 0 false Show File Toolbar Show File Toolbar Show File Toolbar false Show color selector Show color selector Show color selector Shift+I false Show MyPaint shade selector Show MyPaint shade selector Show MyPaint shade selector Shift+M false Show minimal shade selector Show minimal shade selector Show minimal shade selector Shift+N false Show color history Show color history Show color history H false Show common colors Show common colors Show common colors U false Show Tool Options Show Tool Options Show Tool Options \ false Show Brush Editor Show Brush Editor Show Brush Editor F5 false Show Brush Presets Show Brush Presets Show Brush Presets F6 false Toggle Tablet Debugger Toggle Tablet Debugger Toggle Tablet Debugger 0 0 Ctrl+Shift+T false Show Krita log for bug reports. Show Krita log for bug reports. Show Krita log for bug reports. false Show system information for bug reports. Show system information for bug reports. Show system information for bug reports. false Rename Composition... Rename Composition Rename Composition 0 0 false Update Composition Update Composition Update Composition 0 0 false Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale Use multiple of 2 for pixel scale 1 0 true &Invert Selection Invert current selection Invert Selection 10000000000 100 Ctrl+Shift+I false Create Snapshot Create Snapshot 1 0 false Switch to Selected Snapshot Switch to selected snapshot 1 0 false Remove Selected Snapshot Remove Selected Snapshot 1 0 false Painting lightness-increase Make brush color lighter Make brush color lighter Make brush color lighter 0 0 L false lightness-decrease Make brush color darker Make brush color darker Make brush color darker 0 0 K false Make brush color more saturated Make brush color more saturated Make brush color more saturated false Make brush color more desaturated Make brush color more desaturated Make brush color more desaturated false Shift brush color hue clockwise Shift brush color hue clockwise Shift brush color hue clockwise false Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise false Make brush color more red Make brush color more red Make brush color more red false Make brush color more green Make brush color more green Make brush color more green false Make brush color more blue Make brush color more blue Make brush color more blue false Make brush color more yellow Make brush color more yellow Make brush color more yellow false opacity-increase Increase opacity Increase opacity Increase opacity 0 0 O false opacity-decrease Decrease opacity Decrease opacity Decrease opacity 0 0 I false draw-eraser Set eraser mode Set eraser mode Set eraser mode 10000 0 E true view-refresh Reload Original Preset Reload Original Preset Reload Original Preset 10000 false transparency-unlocked Preserve Alpha Preserve Alpha Preserve Alpha 10000 true transform_icons_penPressure Use Pen Pressure Use Pen Pressure Use Pen Pressure 10000 true symmetry-horizontal Horizontal Mirror Tool Horizontal Mirror Tool Horizontal Mirror Tool 0 true symmetry-vertical Vertical Mirror Tool Vertical Mirror Tool Vertical Mirror Tool 0 true Hide Mirror X Line Hide Mirror X Line Hide Mirror X Line 10000 true Hide Mirror Y Line Hide Mirror Y Line Hide Mirror Y Line 10000 true Lock Lock X Line Lock X Line 10000 true Lock Y Line Lock Y Line Lock Y Line 10000 true Move to Canvas Center X Move to Canvas Center X Move to Canvas Center X 10000 false Move to Canvas Center Y Move to Canvas Center Y Move to Canvas Center Y 10000 false &Toggle Selection Display Mode Toggle Selection Display Mode Toggle Selection Display Mode 0 0 false Next Favourite Preset Next Favourite Preset Next Favourite Preset , false Previous Favourite Preset Previous Favourite Preset Previous Favourite Preset . false preset-switcher Switch to Previous Preset Switch to Previous Preset Switch to Previous Preset / false Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar true Reset Foreground and Background Color Reset Foreground and Background Color Reset Foreground and Background Color D false Swap Foreground and Background Color Swap Foreground and Background Color Swap Foreground and Background Color X false Selection Mode: Add Selection Mode: Add Selection Mode: Add A false Selection Mode: Subtract Selection Mode: Subtract Selection Mode: Subtract S false Selection Mode: Intersect Selection Mode: Intersect Selection Mode: Intersect false Selection Mode: Replace Selection Mode: Replace Selection Mode: Replace R false smoothing-weighted Brush Smoothing: Weighted Brush Smoothing: Weighted Brush Smoothing: Weighted false smoothing-no Brush Smoothing: Disabled Brush Smoothing: Disabled Brush Smoothing: Disabled false smoothing-stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer false brushsize-decrease Decrease Brush Size Decrease Brush Size Decrease Brush Size 0 0 [ false smoothing-basic Brush Smoothing: Basic Brush Smoothing: Basic Brush Smoothing: Basic false brushsize-increase Increase Brush Size Increase Brush Size Increase Brush Size 0 0 ] false Toggle Snap To Assistants Toggle Snap to Assistants ToggleAssistant Ctrl+Shift+L true Undo Polygon Selection Points Undo Polygon Selection Points Undo Polygon Selection Points Shift+Z false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false Convert &to Shape Convert to Shape Convert to Shape 10000000000 0 false &Show Global Selection Mask Shows global selection as a usual selection mask in <interface>Layers</interface> docker Show Global Selection Mask 100000 100 true Filters color-to-alpha &Color to Alpha... Color to Alpha Color to Alpha 10000 0 false &Top Edge Detection Top Edge Detection Top Edge Detection 10000 0 false &Index Colors... Index Colors Index Colors 10000 0 false Emboss Horizontal &Only Emboss Horizontal Only Emboss Horizontal Only 10000 0 false D&odge Dodge Dodge 10000 0 false &Sharpen Sharpen Sharpen 10000 0 false B&urn Burn Burn 10000 0 false &Mean Removal Mean Removal Mean Removal 10000 0 false &Gaussian Blur... Gaussian Blur Gaussian Blur 10000 0 false Emboss &in All Directions Emboss in All Directions Emboss in All Directions 10000 0 false &Small Tiles... Small Tiles Small Tiles 10000 0 false &Levels... Levels Levels 10000 0 Ctrl+L false &Sobel... Sobel Sobel 10000 0 false &Wave... Wave Wave 10000 0 false &Motion Blur... Motion Blur Motion Blur 10000 0 false &Invert Invert Invert 10000 0 Ctrl+I false &Color Adjustment curves... Color Adjustment curves Color Adjustment curves 10000 0 Ctrl+M false Pi&xelize... Pixelize Pixelize 10000 0 false Emboss (&Laplacian) Emboss (Laplacian) Emboss (Laplacian) 10000 0 false &Left Edge Detection Left Edge Detection Left Edge Detection 10000 0 false &Blur... Blur Blur 10000 0 false &Raindrops... Raindrops Raindrops 10000 0 false &Bottom Edge Detection Bottom Edge Detection Bottom Edge Detection 10000 0 false &Random Noise... Random Noise Random Noise 10000 0 false &Brightness/Contrast curve... Brightness/Contrast curve Brightness/Contrast curve 10000 0 false Colo&r Balance... Color Balance Color Balance 10000 0 Ctrl+B false &Phong Bumpmap... Phong Bumpmap Phong Bumpmap 10000 0 false &Desaturate Desaturate Desaturate 10000 0 Ctrl+Shift+U false Color &Transfer... Color Transfer Color Transfer 10000 0 false Emboss &Vertical Only Emboss Vertical Only Emboss Vertical Only 10000 0 false &Lens Blur... Lens Blur Lens Blur 10000 0 false M&inimize Channel Minimize Channel Minimize Channel 10000 0 false M&aximize Channel Maximize Channel Maximize Channel 10000 0 false &Oilpaint... Oilpaint Oilpaint 10000 0 false &Right Edge Detection Right Edge Detection Right Edge Detection 10000 0 false &Auto Contrast Auto Contrast Auto Contrast 10000 0 false &Round Corners... Round Corners Round Corners 10000 0 false &Unsharp Mask... Unsharp Mask Unsharp Mask 10000 0 false &Emboss with Variable Depth... Emboss with Variable Depth Emboss with Variable Depth 10000 0 false Emboss &Horizontal && Vertical Emboss Horizontal & Vertical Emboss Horizontal & Vertical 10000 0 false Random &Pick... Random Pick Random Pick 10000 0 false &Gaussian Noise Reduction... Gaussian Noise Reduction Gaussian Noise Reduction 10000 0 false &Posterize... Posterize Posterize 10000 0 false &Wavelet Noise Reducer... Wavelet Noise Reducer Wavelet Noise Reducer 10000 0 false &HSV Adjustment... HSV Adjustment HSV Adjustment 10000 0 Ctrl+U false Tool Shortcuts Dynamic Brush Tool Dynamic Brush Tool Dynamic Brush Tool false Rectangle Tool Rectangle Tool Rectangle Tool false Multibrush Tool Multibrush Tool Multibrush Tool Q false Colorize Mask Tool Colorize Mask Tool Colorize Mask Tool Smart Patch Tool Smart Patch Tool Smart Patch Tool Pan Tool Pan Tool Pan Tool Select Shapes Tool Select Shapes Tool Select Shapes Tool false Color Picker Select a color from the image or current layer Select a color from the image or current layer P false Fill Tool Fill a contiguous area of color with a color, or fill a selection. Fill a contiguous area of color with a color, or fill a selection. F false Line Tool Line Tool Line Tool false Ellipse Tool Ellipse Tool Ellipse Tool false Freehand Brush Tool Freehand Brush Tool Freehand Brush Tool B false Create object Create object Create object false Pattern editing Pattern editing Pattern editing false Review Review Review false Draw a gradient. Draw a gradient. Draw a gradient. G false Measurement Tool Measure the distance between two points Measure the distance between two points false Move Tool Move a layer Move a layer T false Vector Image Tool Vector Image (EMF/WMF/SVM/SVG) tool Vector Image (EMF/WMF/SVM/SVG) tool false Calligraphy Calligraphy Calligraphy false Edit Shapes Tool Edit Shapes Tool Edit Shapes Tool false Zoom Tool Zoom Tool Zoom Tool false Gradient Editing Tool Gradient editing Gradient editing false Reference Images Tool Reference Images Tool Reference Images Tool false Blending Modes Select Normal Blending Mode Select Normal Blending Mode Select Normal Blending Mode 0 0 Alt+Shift+N false Select Dissolve Blending Mode Select Dissolve Blending Mode Select Dissolve Blending Mode 0 0 Alt+Shift+I false Select Behind Blending Mode Select Behind Blending Mode Select Behind Blending Mode 0 0 Alt+Shift+Q false Select Clear Blending Mode Select Clear Blending Mode Select Clear Blending Mode 0 0 Alt+Shift+R false Select Darken Blending Mode Select Darken Blending Mode Select Darken Blending Mode 0 0 Alt+Shift+K false Select Multiply Blending Mode Select Multiply Blending Mode Select Multiply Blending Mode 0 0 Alt+Shift+M false Select Color Burn Blending Mode Select Color Burn Blending Mode Select Color Burn Blending Mode 0 0 Alt+Shift+B false Select Linear Burn Blending Mode Select Linear Burn Blending Mode Select Linear Burn Blending Mode 0 0 Alt+Shift+A false Select Lighten Blending Mode Select Lighten Blending Mode Select Lighten Blending Mode 0 0 Alt+Shift+G false Select Screen Blending Mode Select Screen Blending Mode Select Screen Blending Mode 0 0 Alt+Shift+S false Select Color Dodge Blending Mode Select Color Dodge Blending Mode Select Color Dodge Blending Mode 0 0 Alt+Shift+D false Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode 0 0 Alt+Shift+W false Select Overlay Blending Mode Select Overlay Blending Mode Select Overlay Blending Mode 0 0 Alt+Shift+O false Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode 0 0 Alt+Shift+P false Select Soft Light Blending Mode Select Soft Light Blending Mode Select Soft Light Blending Mode 0 0 Alt+Shift+F false Select Hard Light Blending Mode Select Hard Light Blending Mode Select Hard Light Blending Mode 0 0 Alt+Shift+H false Select Vivid Light Blending Mode Select Vivid Light Blending Mode Select Vivid Light Blending Mode 0 0 Alt+Shift+V false Select Linear Light Blending Mode Select Linear Light Blending Mode Select Linear Light Blending Mode 0 0 Alt+Shift+J false Select Pin Light Blending Mode Select Pin Light Blending Mode Select Pin Light Blending Mode 0 0 Alt+Shift+Z false Select Hard Mix Blending Mode Select Hard Mix Blending Mode Select Hard Mix Blending Mode 0 0 Alt+Shift+L false Select Difference Blending Mode Select Difference Blending Mode Select Difference Blending Mode 0 0 Alt+Shift+E false Select Exclusion Blending Mode Select Exclusion Blending Mode Select Exclusion Blending Mode 0 0 Alt+Shift+X false Select Hue Blending Mode Select Hue Blending Mode Select Hue Blending Mode 0 0 Alt+Shift+U false Select Saturation Blending Mode Select Saturation Blending Mode Select Saturation Blending Mode 0 0 Alt+Shift+T false Select Color Blending Mode Select Color Blending Mode Select Color Blending Mode 0 0 Alt+Shift+C false Select Luminosity Blending Mode Select Luminosity Blending Mode Select Luminosity Blending Mode 0 0 Alt+Shift+Y false Animation prevframe - Previous frame + Previous Frame - Move to previous frame - Move to previous frame + Move selection to previous frame + Move selection to previous frame 1 0 false nextframe - Next frame + Next Frame - Move to next frame - Move to next frame + Move selection to next frame + Move selection to next frame + 1 + 0 + + false + + + + prevkeyframe + Previous Keyframe + + Move selection to previous keyframe + Move selection to previous keyframe + 1 + 0 + + false + + + + nextkeyframe + Next Keyframe + + Move selection to next keyframe + Move selection to next keyframe + 1 + 0 + + false + + + + prevkeyframe + Previous Matching Keyframe + + Move selection to previous keyframe of the same color + Move selection to previous matching keyframe + 1 + 0 + + false + + + + nextkeyframe + Next Matching Keyframe + + Move selection to next keyframe of the same color + Move selection to next matching keyframe 1 0 false animation_play Play / pause animation Play / pause animation Play / pause animation 1 0 false animation_stop Stop animation Stop animation Stop animation 1 0 false addblankframe Create Blank Frame Add blank frame Add blank frame 100000 0 false addduplicateframe Create Duplicate Frame Add duplicate frame Add duplicate frame 100000 0 false Toggle onion skin Toggle onion skin Toggle onion skin 100000 0 false Previous Keyframe false Next Keyframe false First Frame false Last Frame false Auto Frame Mode true dropframe Drop Frames Enable to preserve playback timing. true Pin to Timeline If checked, layer becomes pinned to the timeline, making it visible even when inactive. true Insert Keyframe Left Insert keyframes to the left of selection, moving the tail of animation to the right. 100000 0 false Insert Keyframe Right Insert keyframes to the right of selection, moving the tail of animation to the right. 100000 0 false Insert Multiple Keyframes Insert several keyframes based on user parameters. 100000 0 false Remove Frame and Pull Remove keyframes moving the tail of animation to the left 100000 0 false deletekeyframe Remove Keyframe Remove keyframes without moving anything around 100000 0 false Insert Column Left Insert column to the left of selection, moving the tail of animation to the right 100000 0 false Insert Column Right Insert column to the right of selection, moving the tail of animation to the right 100000 0 false Insert Multiple Columns Insert several columns based on user parameters. 100000 0 false Remove Column and Pull Remove columns moving the tail of animation to the left 100000 0 false Remove Column Remove columns without moving anything around 100000 0 false Insert Hold Frame Insert a hold frame after every keyframe 100000 0 false Insert Multiple Hold Frames Insert N hold frames after every keyframe 100000 0 false Remove Hold Frame Remove a hold frame after every keyframe 100000 0 false Remove Multiple Hold Frames Remove N hold frames after every keyframe 100000 0 false Insert Hold Column Insert a hold column into the frame at the current position 100000 0 false Insert Multiple Hold Columns Insert N hold columns into the frame at the current position 100000 0 false Remove Hold Column Remove a hold column from the frame at the current position 100000 0 false Remove Multiple Hold Columns Remove N hold columns from the frame at the current position 100000 0 false Add opacity keyframe Adds keyframe to control layer opacity 100000 0 false Remove opacity keyframe Removes keyframe to control layer opacity 100000 0 false Mirror Frames Mirror frames' position 100000 0 false Mirror Columns Mirror columns' position 100000 0 false Copy to Clipboard Copy frames to clipboard 100000 0 false Cut to Clipboard Cut frames to clipboard 100000 0 false Paste from Clipboard Paste frames from clipboard 100000 0 false Copy Columns to Clipboard Copy columns to clipboard 100000 0 false Cut Columns to Clipboard Cut columns to clipboard 100000 0 false Paste Columns from Clipboard Paste columns from clipboard 100000 0 false Set Start Time 100000 0 false Set End Time 100000 0 false Update Playback Range 100000 0 false Layers Activate next layer Activate next layer Activate next layer 1000 0 PgUp false Activate next sibling layer, skipping over groups. Activate next sibling layer Activate next sibling layer 1000 0 false Activate previous layer Activate previous layer Activate previous layer 1000 0 PgDown false Activate previous sibling layer, skipping over groups. Activate previous sibling layer Activate previous sibling layer 1000 0 false Activate previously selected layer Activate previously selected layer Activate previously selected layer 1000 0 ; false groupLayer &Group Layer Group Layer Group Layer 1000 0 false cloneLayer &Clone Layer Clone Layer Clone Layer 1000 0 false vectorLayer &Vector Layer Vector Layer Vector Layer 1000 0 false filterLayer &Filter Layer... Filter Layer Filter Layer 1000 0 false fillLayer &Fill Layer... Fill Layer Fill Layer 1000 0 false fileLayer &File Layer... File Layer File Layer 1000 0 false transparencyMask &Transparency Mask Transparency Mask Transparency Mask 100000 0 false filterMask &Filter Mask... Filter Mask Filter Mask 100000 0 false filterMask &Colorize Mask Colorize Mask Colorize Mask 100000 0 false transformMask &Transform Mask... Transform Mask Transform Mask 100000 0 false selectionMask &Local Selection Local Selection Local Selection 100000 0 false view-filter &Isolate Active Layer Isolate Active Layer Isolate Active Layer 1000 0 true view-filter &Isolate Active Group Isolate Active Group Isolate Active Group 1000 0 true layer-locked &Toggle layer lock Toggle layer lock Toggle layer lock 1000 0 false visible Toggle layer &visibility Toggle layer visibility Toggle layer visibility 1000 0 false + + visible + Toggle Layer Soloing + + Toggle layer soloing, temporarily disabling visibility of all non-child layers. + Toggle layer soloing + 1000 + 0 + + false + + transparency-locked Toggle layer &alpha Toggle layer alpha Toggle layer alpha 1000 0 false transparency-enabled Toggle layer alpha &inheritance Toggle layer alpha inheritance Toggle layer alpha inheritance 1000 0 false paintLayer &Paint Layer Paint Layer Paint Layer 1000 0 Insert false &New Layer From Visible New layer from visible New layer from visible 1000 0 false duplicatelayer &Duplicate Layer or Mask Duplicate Layer or Mask Duplicate Layer or Mask 1000 0 Ctrl+J false &Cut Selection to New Layer Cut Selection to New Layer Cut Selection to New Layer 100000000 1 Ctrl+Shift+J false Copy &Selection to New Layer Copy Selection to New Layer Copy Selection to New Layer 100000000 0 Ctrl+Alt+J false Copy Layer Copy layer to clipboard Copy layer to clipboard 1000 0 false Cut Layer Cut layer to clipboard Cut layer to clipboard 1000 0 false Paste Layer Paste layer from clipboard Paste layer from clipboard 1000 0 false Quick Group Create a group layer containing selected layers Quick Group 1000 0 Ctrl+G false Quick Ungroup Remove grouping of the layers or remove one layer out of the group Quick Ungroup 100000 0 Ctrl+Alt+G false Quick Clipping Group Group selected layers and add a layer with clipped alpha channel Quick Clipping Group 100000 0 Ctrl+Shift+G false All Layers Select all layers Select all layers 10000 0 false Visible Layers Select all visible layers Select all visible layers 10000 0 false Locked Layers Select all locked layers Select all locked layers 10000 0 false Invisible Layers Select all invisible layers Select all invisible layers 10000 0 false Unlocked Layers Select all unlocked layers Select all unlocked layers 10000 0 false document-save &Save Layer/Mask... Save Layer/Mask Save Layer/Mask 1000 0 false document-save Save Vector Layer as SVG... Save Vector Layer as SVG Save Vector Layer as SVG 1000 0 false document-save Save &Group Layers... Save Group Layers Save Group Layers 100000 0 false Convert group to &animated layer Convert child layers into animation frames Convert child layers into animation frames 100000 0 false Convert to &animated layer Convert layer into animation frames Convert layer into animation frames 100000 0 false fileLayer to &File Layer Saves out the layers into a new image and then references that image. Convert to File Layer 100000 0 false I&mport Layer... Import Layer Import Layer 100000 0 false paintLayer &as Paint Layer... as Paint Layer as Paint Layer 1000 0 false transparencyMask as &Transparency Mask... as Transparency Mask as Transparency Mask 1000 0 false filterMask as &Filter Mask... as Filter Mask as Filter Mask 1000 0 false selectionMask as &Selection Mask... as Selection Mask as Selection Mask 1000 0 false paintLayer to &Paint Layer to Paint Layer to Paint Layer 1000 0 false transparencyMask to &Transparency Mask to Transparency Mask to Transparency Mask 1000 0 false filterMask to &Filter Mask... to Filter Mask to Filter Mask 1000 0 false selectionMask to &Selection Mask to Selection Mask to Selection Mask 1000 0 false transparencyMask &Alpha into Mask Alpha into Mask Alpha into Mask 100000 10 false transparency-enabled &Write as Alpha Write as Alpha Write as Alpha 1000000 1 false document-save &Save Merged... Save Merged Save Merged 1000000 0 false split-layer Split Layer... Split Layer Split Layer 1000 0 false Wavelet Decompose ... Wavelet Decompose Wavelet Decompose 1000 1 false symmetry-horizontal Mirror Layer Hori&zontally Mirror Layer Horizontally Mirror Layer Horizontally 1000 1 false symmetry-vertical Mirror Layer &Vertically Mirror Layer Vertically Mirror Layer Vertically 1000 1 false &Rotate Layer... Rotate Layer Rotate Layer 1000 1 false object-rotate-right Rotate &Layer 90° to the Right Rotate Layer 90° to the Right Rotate Layer 90° to the Right 1000 1 false object-rotate-left Rotate Layer &90° to the Left Rotate Layer 90° to the Left Rotate Layer 90° to the Left 1000 1 false Rotate Layer &180° Rotate Layer 180° Rotate Layer 180° 1000 1 false Scale &Layer to new Size... Scale Layer to new Size Scale Layer to new Size 100000 1 false &Shear Layer... Shear Layer Shear Layer 1000 1 false symmetry-horizontal Mirror All Layers Hori&zontally Mirror All Layers Horizontally Mirror All Layers Horizontally 1000 1 false symmetry-vertical Mirror All Layers &Vertically Mirror All Layers Vertically Mirror All Layers Vertically 1000 1 false &Rotate All Layers... Rotate All Layers Rotate All Layers 1000 1 false object-rotate-right Rotate All &Layers 90° to the Right Rotate All Layers 90° to the Right Rotate All Layers 90° to the Right 1000 1 false object-rotate-left Rotate All Layers &90° to the Left Rotate All Layers 90° to the Left Rotate All Layers 90° to the Left 1000 1 false Rotate All Layers &180° Rotate All Layers 180° Rotate All Layers 180° 1000 1 false Scale All &Layers to new Size... Scale All Layers to new Size Scale All Layers to new Size 100000 1 false &Shear All Layers... Shear All Layers Shear All Layers 1000 1 false &Offset Layer... Offset Layer Offset Layer 100000 1 false Clones &Array... Clones Array Clones Array 100000 0 false &Edit metadata... Edit metadata Edit metadata 100000 1 false &Histogram... Histogram Histogram 100000 0 false &Convert Layer Color Space... Convert Layer Color Space Convert Layer Color Space 100000 1 false merge-layer-below &Merge with Layer Below Merge with Layer Below Merge with Layer Below 100000 0 Ctrl+E false &Flatten Layer Flatten Layer Flatten Layer 100000 0 false Ras&terize Layer Rasterize Layer Rasterize Layer 10000000 1 false Flatten ima&ge Flatten image Flatten image 100000 0 Ctrl+Shift+E false La&yer Style... Layer Style Layer Style 100000 1 false Move into previous group Move into previous group Move into previous group 0 0 false Move into next group Move into next group Move into next group 0 0 false Rename current layer Rename current layer Rename current layer 100000 0 F2 false deletelayer &Remove Layer Remove Layer Remove Layer 1000 1 Shift+Delete false arrowupblr Move Layer or Mask Up Move Layer or Mask Up Ctrl+PgUp false arrowdown Move Layer or Mask Down Move Layer or Mask Down Ctrl+PgDown false properties &Properties... Properties Properties 1000 1 F3 false Set Copy F&rom... Set the source for the selected clone layer(s). Set Copy From 1000 1 false diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index 8aed5fc095..c01244de01 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,492 +1,505 @@ org.kde.krita org.kde.krita.desktop CC0-1.0 GPL-3.0-only Krita Foundation Fundació Krita Fundació Krita Krita Foundation Krita Foundation Krita Foundation Fundación Krita Krita sihtasutus Krita Fundazioa Krita Foundation La fondation Krita Fundación Krita Krita Foundation (Fundatiomn de Krita) Asas Krita Fondazione Krita Krita Foundation Krita Foundation Krita Foundation Fundacja Krity Fundação do Krita Krita Foundation Nadácia Krita + Fundacija Krita Krita-stiftelsen Krita Vakfı Фундація Krita xxKrita Foundationxx Krita 基金会 Krita 基金會 foundation@krita.org Krita كريتا Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita + Krita Krita Krita Krita xxKritaxx Krita Krita Digital Painting, Creative Freedom رسم رقميّ، حريّة إبداعيّة Digitalno crtanje, kreativna sloboda Dibuix digital, Llibertat creativa Dibuix digital, Llibertat creativa Digitální malování, svoboda tvorby Digital tegning, kunstnerisk frihed Digitales Malen, kreative Freiheit Ψηφιακή ζωγραφική, δημιουργική ελευθερία Digital Painting, Creative Freedom Pintura digital, libertad creativa Digitaalne joonistamine, loominguline vabadus Margolan digitala, sormen askatasuna Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pelukisan Digital, Kebebasan Berkreatif Pittura digitale, libertà creativa 디지털 페인팅, 자유로운 창의성 Digital Painting, Creative Freedom Digital teikning – kreativ fridom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura digital, liberdade criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda + Digitalno slikarstvo, ustvarjalna svoboda Digital målning, kreativ frihet Sayısal Boyama, Yaratıcı Özgürlük Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 自由挥洒数字绘画的无限创意 數位繪畫,創作自由

Krita is the full-featured digital art studio.

Krita je potpuni digitalni umjetnički studio.

El Krita és l'estudi d'art digital ple de funcionalitats.

El Krita és l'estudi d'art digital ple de funcionalitats.

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

Το Krita είναι ένα πλήρες χαρακτηριστικών ψηφιακό ατελιέ.

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

Krita arte lantegi digital osoa da.

Krita on täyspiirteinen digitaiteen ateljee.

Krita est le studio d'art numérique complet.

Krita é un estudio completo de arte dixital.

Krita es le studio de arte digital complete.

Krita adalah studio seni digital yang penuh dengan fitur.

Krita è uno studio d'arte digitale completo.

Krita は、フル機能を備えたデジタルなアートスタジオです。

Krita는 디지털 예술 스튜디오입니다.

Krita is de digitale kunststudio vol mogelijkheden.

Krita er ei funksjonsrik digital teiknestove.

Krita jest pełnowymiarowym, cyfrowym studiem artystycznym

O Krita é o estúdio de arte digital completo.

O Krita é o estúdio de arte digital completo.

Krita — полнофункциональный инструмент для создания цифровой графики.

Krita je plne vybavené digitálne umelecké štúdio.

+

Krita je celovit digitalni umetniški studio.

Krita är den fullfjädrade digitala konststudion.

Krita, tam özellikli dijital sanat stüdyosudur.

Krita — повноцінний комплекс для створення цифрових художніх творів.

xxKrita is the full-featured digital art studio.xx

Krita 是一款功能齐全的数字绘画工作室软件。

Krita 是全功能的數位藝術工作室。

It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.

On je savršen za skiciranje i slikanje i predstavlja finalno rješenje za kreiranje digitalnih slika od nule s majstorima

És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.

És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.

Είναι ιδανικό για σκιτσογραφία και ζωγραφική, και παρουσιάζει μια από άκρη σε άκρη λύση για τη δημιουργία από το μηδέν αρχείων ψηφιακης ζωγραφικής από τους δασκάλους της τέχνης.

It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.

Es perfecto para diseñar y pintar, y ofrece una solución completa para crear desde cero archivos de pintura digital apta para profesionales.

See on suurepärane töövahend visandite ja joonistuste valmistamiseks ning annab andekatele kunstnikele võimaluse luua digitaalpilt algusest lõpuni just oma käe järgi.

Zirriborratzeko eta margotzeko ezin hobea da, eta margolan digitalen fitxategiak hutsetik sortzeko muturretik-muturrera konponbide bat aurkezten du, maisuentzako mailakoa.

Se on täydellinen luonnosteluun ja maalaukseen ja tarjoaa kokonaisratkaisun digitaalisten kuvatiedostojen luomiseen alusta alkaen.

Il est parfait pour crayonner et peindre, et constitue une solution de bout en bout pour créer des fichier de peinture numérique depuis la feuille blanche jusqu'au épreuves finales.

Resulta perfecto para debuxar e pintar, e presenta unha solución completa que permite aos mestres crear ficheiros de debuxo dixital desde cero.

Illo es perfecte pro schizzar e pinger, e presenta un solution ab fin al fin pro crear files de pictura digital ab grattamentos per maestros.

Ini adalah sempurna untuk mensketsa dan melukis, dan menghadirkan sebuah solusi untuk menciptakan file-file pelukisan digital dari goresan si pelukis ulung.

Perfetto per fare schizzi e dipingere, prevede una soluzione completa che consente agli artisti di creare file di dipinti digitali partendo da zero.

스케치, 페인팅에 사용할 완벽한 도구이며, 생각으로부터 디지털 페인팅 파일을 만들어 낼 수 있는 종합적인 도구를 제공합니다.

Het is perfect voor schetsen en schilderen en zet een end–to–end oplossing voor het maken van digitale bestanden voor schilderingen vanuit het niets door meesters.

Passar perfekt til både teikning og måling, og dekkjer alle ledd i prosessen med å laga digitale måleri frå grunnen av.

Nadaje się perfekcyjnie do szkicowania i malowania i dostarcza zupełnego rozwiązania dla tworzenia plików malowideł cyfrowych od zalążka.

É perfeito para desenhos e pinturas, oferecendo uma solução final para criar ficheiros de pintura digital do zero por mestres.

É perfeito para desenhos e pinturas, oferecendo uma solução final para criar arquivos de desenho digital feitos a partir do zero por mestres.

Она превосходно подходит для набросков и рисования, предоставляя мастерам самодостаточный инструмент для создания цифровой живописи с нуля.

Je ideálna na skicovanie a maľovanie a poskytuje end-to-end riešenie na vytváranie súborov digitálneho maľovania od základu od profesionálov.

+

Idealen je za skiciranje in slikanje ter predstavlja rešitev od začetka do konca za ustvarjanje mojstrskih datotek digitalne slike iz nič.

Den är perfekt för att skissa och måla, samt erbjuder en helomfattande lösning för att skapa digitala målningsfiler från grunden av mästare.

Eskiz ve boyama için mükemmeldir ve ustaların sıfırdan dijital boyama dosyaları oluşturmak için uçtan-uca bir çözüm sunar.

Цей комплекс чудово пасує для створення ескізів та художніх зображень і є самодостатнім набором для створення файлів цифрових полотен «з нуля» для справжніх художників.

xxIt is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.xx

它专门为数字绘画设计,为美术工作者提供了一个从起草、上色到完成作品等整个创作流程的完整解决方案。

它是素描和繪畫的完美選擇,並提供了一個從零開始建立數位繪畫檔的端到端解決方案。

Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.

Krita je odličan izbor za kreiranje konceptualne umjetnosti, stripove, teksture za obradu i mat slike. Krita podržava mnoge prostore boja kao RGB i CMIK na 8 i 16 bitnim cjelobrojnim kanalimaa, kao i 16 i 32 bita floating point kanalima.

El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.

El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.

Το Krita είναι μια εξαιρετική επιλογή για τη δημιουργία αφηρημένης τέχνης, ιστοριών με εικόνες, υφής για ζωγραφική αποτύπωσης και διάχυσης φωτός. Το Krita υποστηρίζει πολλούς χρωματικούς χώρους όπως τα RGB και CMYK σε 8 και 16 bit κανάλια ακεραίων καθώς επίσης και σε 16 και 32 bit κανάλια κινητής υποδιαστολής,

Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colourspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.

Krita es una gran elección para crear arte conceptual, cómics, texturas para renderizar y «matte paintings». Krita permite el uso de muchos espacios de color, como, por ejemplo, RGB y CMYK, tanto en canales de enteros de 8 y 16 bits, así como en canales de coma flotante de 16 y 32 bits.

Krita on üks paremaid valikuid kontseptuaalkunsti, koomiksite, tekstuuride ja digitaalmaalide loomiseks. Krita toetab paljusid värviruume, näiteks RGB ja CMYK 8 ja 16 täisarvulise bitiga kanali kohta, samuti 16 ja 32 ujukomabitiga kanali kohta.

Krita aukera bikaina da kontzeptuzko artea, komikiak, errendatzeko ehundurak eta «matte» margolanak sortzeko. Kritak kolore-espazio ugari onartzen ditu hala nola GBU eta CMYK, 8 eta 16 biteko osoko kanaletan, baita 16 eta 32 biteko koma-higikorreko kanaletan.

Krita on hyvä valinta konseptikuvituksen, sarjakuvien, pintakuvioiden ja maalausten luomiseen. Krita tukee useita väriavaruuksia kuten RGB:tä ja CMYK:ta 8 ja 16 bitin kokonaisluku- samoin kuin 16 ja 32 bitin liukulukukanavin.

Krita est un très bon choix pour créer des concepts arts, des bandes-dessinées, des textures de rendu et des peintures. Krita prend en charge plusieurs espaces de couleurs comme « RVB » et « CMJN » avec les canaux de 8 et 16 bits entiers ainsi que les canaux de 16 et 32 bits flottants.

Krita é unha gran opción para crear arte conceptual, texturas para renderización e pinturas mate. Krita permite usar moitos espazos de cores como RGB e CMYK con canles de 8 e 16 bits, así como canles de coma flotante de 16 e 32 bits.

Krita es un grande selection pro crear arte de concepto, comics, texturas pro rendering e picturas opac. Krita supporta multe spatios de colores como RGB e CMYK con canales de integer a 8 e 16 bits, como anque canales floating point a 16 e 32 bits.

Krita adalah pilihan yang cocok untuk menciptakan konsep seni, komik, tekstur untuk rendering dan lukisan matte. Krita mendukung banyak ruang warna seperti RGB dan CMYK pada channel integer 8 dan 16 bit, serta channel floating point 16 dan 32 bit.

Krita rappresenta una scelta ottimale per la creazione di arte concettuale, fumetti e texture per il rendering e il matte painting. Krita supporta molti spazi colori come RGB e CMYK a 8 e 16 bit per canali interi e 16 e 32 bit per canali a virgola mobile.

コンセプトアート、コミック、3DCG 用テクスチャ、マットペイントを制作する方にとって、Krita は最適な選択です。Krita は、8/16 ビット整数/チャンネル、および 16/32 ビット浮動小数点/チャンネルの RGB や CMYK をはじめ、さまざまな色空間をサポートしています。

Krita는 컨셉 아트, 만화, 렌더링용 텍스처, 풍경화 등을 그릴 때 사용할 수 있는 완벽한 도구입니다. RGB, CMYK와 같은 여러 색 공간 및 8비트/16비트 정수 채널, 16비트/32비트 부동 소수점 채널을 지원합니다.

Krita is een goede keuze voor het maken van kunstconcepten, strips, textuur voor weergeven en matte schilderijen. Krita ondersteunt vele kleurruimten zoals RGB en CMYK in 8 en 16 bits kanalen met gehele getallen, evenals 16 en 32 bits kanalen met drijvende komma.

Krita er det ideelle valet dersom du vil laga konseptskisser, teikneseriar, teksturar for 3D-rendering eller «matte paintings». Programmet støttar fleire fargerom, både RGB- og CMYK-baserte, med 8- og 16-bits heiltals- eller flyttalskanalar.

Krita jest świetnym wyborem przy tworzeniu koncepcyjnej sztuki, komiksów, tekstur do wyświetlania i kaszet. Krita obsługuje wiele przestrzeni barw takich jak RGB oraz CMYK dla kanałów 8 oraz 16 bitowych wyrażonych w l. całkowitych, a także 16 oraz 32 bitowych wyrażonych w l. zmiennoprzecinkowych.

O Krita é uma óptima escolha para criar arte conceptual, banda desenhada, texturas para desenho e pinturas. O Krita suporta diversos espaços de cores como o RGB e o CMYK com canais de cores inteiros a 8 e 16 bits, assim como canais de vírgula flutuante a 16 e a 32 bits.

O Krita é uma ótima escolha para criação de arte conceitual, histórias em quadrinhos, texturas para desenhos e pinturas. O Krita tem suporte a diversos espaços de cores como RGB e CMYK com canais de cores inteiros de 8 e 16 bits, assim como canais de ponto flutuante de 16 e 32 bits.

Krita — отличный выбор для создания концепт-артов, комиксов, текстур для рендеринга и рисования. Она поддерживает множество цветовых пространств включая RGB и CMYK с 8 и 16 целыми битами на канал, а также 16 и 32 битами с плавающей запятой на канал.

Krita je výborná voľba pre vytváranie konceptového umenia, textúr na renderovanie a matné kresby. Krita podporuje mnoho farebných priestorov ako RGB a CMYK na 8 a 16 bitových celočíselných kanáloch ako aj 16 a 32 bitových reálnych kanáloch.

+

Krita je odlična izbira za ustvarjanje konceptualne umetnosti, stripov, tekstur za upodabljanje in mat slike. Krita podpira številne barvne prostore, kot sta RGB in CMYK na 8 in 16 bitne celoštevilske kanale, kot tudi 16 in 32 bitne kanale števil v plavajočih vejicah.

Krita är ett utmärkt val för att skapa concept art, serier, strukturer för återgivning och bakgrundsmålningar. Krita stöder många färgrymder som RGB och CMYK med 8- och 16-bitars heltal, samt 16- och 32-bitars flyttal.

Krita, konsept sanat, çizgi roman, kaplama ve mat resimler için dokular oluşturmak için mükemmel bir seçimdir. Krita, 8 ve 16 bit tamsayı kanallarında RGB ve CMYK gibi birçok renk alanını ve 16 ve 32 bit kayan nokta kanallarını desteklemektedir.

Krita — чудовий інструмент для створення концептуального живопису, коміксів, текстур для моделей та декорацій. У Krita передбачено підтримку багатьох просторів кольорів, зокрема RGB та CMYK з 8-бітовими та 16-бітовими цілими значеннями, а також 16-бітовими та 32-бітовими значеннями з рухомою крапкою для каналів кольорів.

xxKrita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.xx

Krita 是绘制概念美术、漫画、纹理和接景的理想选择。Krita 支持多种色彩空间,如 8 位和 16 位整数及 16 位和 32 位浮点的 RGB 和 CMYK 颜色模型。

Krita 是創造概念藝術、漫畫、彩現紋理和場景繪畫的絕佳選擇。Krita 在 8 位元和 16 位元整數色版,以及 16 位元和 32 位元浮點色板中支援 RGB 和 CMYK 等多種色彩空間。

Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.

Zabavite se kreirajući napredne pogone četki, filtere i mnoge praktične osobine koje čine Krita vrlo produktivnim.

Gaudiu pintant amb els motors avançats de pinzells, filtres impressionants i moltes característiques útils que fan el Krita molt productiu.

Gaudiu pintant amb els motors avançats de pinzells, filtres impressionants i moltes característiques útils que fan el Krita molt productiu.

Διασκεδάστε ζωγραφίζοντας με τις προηγμένες μηχανές πινέλων, με εκπληκτικά φίλτρα και πολλά εύκολης χρήσης χαρακτηριστικά που παρέχουν στο Krita εξαιρετικά αυξημένη παραγωγικότητα.

Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.

Diviértase pintando con los avanzados motores de pinceles, los espectaculares filtros y muchas funcionalidades prácticas que hacen que Krita sea enormemente productivo.

Joonistamise muudavad tunduvalt lõbusamaks võimsad pintslimootorid, imetabased filtrid ja veel paljud käepärased võimalused, mis muudavad Krita kasutaja tohutult tootlikuks.

Marrazten ondo pasa ezazu, isipu motor aurreratuekin, iragazki txundigarriekin eta eginbide praktiko ugariekin, zeintzuek Krita ikaragarri emankorra egiten duten.

Pidä hauskaa maalatessasi edistyneillä sivellinmoottoreilla, hämmästyttävillä suotimilla ja monilla muilla kätevillä ominaisuuksilla, jotka tekevät Kritasta tavattoman tehokkaan.

Amusez-vous à peindre avec les outils de brosse avancés, les filtres incroyables et les nombreuses fonctionnalités pratiques qui rendent Krita extrêmement productif.

Goza debuxando con motores de pincel avanzados, filtros fantásticos e moitas outras funcionalidades útiles que fan de Krita un programa extremadamente produtivo.

Amusa te a pinger con le motores de pincel avantiate, filtros stupende e multe characteristicas amical que face Krita enormemente productive.

Bersenang-senanglah melukis dengan mesin kuas canggih, filter luar biasa dan banyak fitur berguna yang membuat Krita sangat produktif.

Divertiti a dipingere con gli avanzati sistemi di pennelli, i sorprendenti filtri e molte altre utili caratteristiche che fanno di Krita un software enormemente produttivo.

Krita のソフトウェアとしての生産性を高めている先進的なブラシエンジンや素晴らしいフィルタのほか、便利な機能の数々をお楽しみください。

Krita의 고급 브러시 엔진, 다양한 필터, 여러 도움이 되는 기능으로 생산성을 즐겁게 향상시킬 수 있습니다.

Veel plezier met schilderen met the geavanceerde penseel-engines, filters vol verbazing en vele handige mogelijkheden die maken dat Krita enorm productief is.

Leik deg med avanserte penselmotorar og fantastiske biletfilter – og mange andre nyttige funksjonar som gjer deg produktiv med Krita.

Baw się przy malowaniu przy użyciu zaawansowanych silników pędzli, zadziwiających filtrów i wielu innych przydatnych cech, które czynią z Krity bardzo produktywną.

Divirta-se a pintar com os motores de pincéis avançados, os filtros espantosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.

Divirta-se pintando com os mecanismos de pincéis avançados, filtros maravilhosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.

Получайте удовольствие от использования особых кистевых движков, впечатляющих фильтров и множества других функций, делающих Krita сверхпродуктивной.

Užívajte si maľovanie s pokročilými kresliacimi enginmi, úžasnými filtrami a mnohými užitočnými funkciami, ktoré robia Kritu veľmi produktívnu.

+

Zabavajte se s slikanjem z naprednimi čopiči, neverjetnimi filtri in številnimi priročnimi funkcijami, zaradi katerih je Krita izjemno produktivna.

Ha det så kul vid målning med de avancerade penselfunktionerna, fantastiska filtren och många praktiska funktioner som gör Krita så enormt produktiv.

Gelişmiş fırça motorları, şaşırtıcı filtreler ve Krita'yı son derece üretken yapan bir çok kullanışlı özellikli boya ile iyi eğlenceler.

Отримуйте задоволення від малювання за допомогою пензлів з найширшими можливостями, чудових фільтрів та багатьох зручних можливостей, які роблять Krita надзвичайно продуктивним засобом малювання.

xxHave fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.xx

Krita 具有功能强大的笔刷引擎、种类繁多的滤镜以及便于操作的交互设计,可让你尽情、高效地挥洒无限创意。

使用先進的筆刷引擎、驚人的濾鏡和許多方便的功能來開心地繪畫,讓 Krita 擁有巨大的生產力。

https://www.krita.org/ https://docs.krita.org/KritaFAQ.html https://krita.org/support-us/donations/ https://docs.krita.org/ https://docs.krita.org/en/untranslatable_pages/reporting_bugs.html Krita is a full-featured digital painting studio El Krita és un estudi de pintura digital ple de funcionalitats El Krita és un estudi de pintura digital ple de funcionalitats Krita is a full-featured digital painting studio Krita es un completo estudio de dibujo digital Krita on rohkete võimalustega digitaalkunstistuudio Krita pintura-digital lantegi osoa bat da Krita est un studio d'art numérique complet. Krita è uno studio d'arte digitale completo Krita는 다기능 디지털 예술 스튜디오입니다 Krita is een digitale schilderstudio vol mogelijkheden Krita er ei funksjonsrik digital teiknestove. O Krita é um estúdio de arte digital completo O Krita é um estúdio de pintura digital completo Krita je plnohodnotné digitálne maliarske štúdio + Krita je celovit digitalni umetniški studio. Krita är en fullfjädrad digital konststudio Krita — повноцінний комплекс для цифрового малювання xxKrita is a full-featured digital painting studioxx Krita 是一款功能齐全的数字绘画软件。 Krita 是全功能的數位繪圖工作室 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png The startup window now also gives you the latest news about Krita La finestra d'inici també ofereix les últimes notícies sobre el Krita La finestra d'inici també ofereix les últimes notícies sobre el Krita The startup window now also gives you the latest news about Krita La ventana de bienvenida también le proporciona ahora las últimas noticias sobre Krita Käivitusaken jagab nüüd ka Krita värskemaid uudiseid Abioko leihoak orain Krita-ri buruzko albiste berrienak ematen dizkizu Maintenant, la fenêtre de démarrage vous donne aussi les dernières informations concernant Krita La finestra di avvio ora fornisce anche le ultime novità su Krita 시작 창에서 Krita의 최신 소식을 볼 수 있습니다 Het opstartvenster geeft u nu ook you het laatste nieuws over Krita Oppstartsvindauget viser no siste nytt om Krita. A janela inicial agora também lhe dá as últimas notícias sobre o Krita A janela de inicialização agora também mostra as últimas notícias sobre o Krita V úvodnom okne sa tiež nachádzajú najnovšie správy o Krita + Začetno okno vam zdaj ponuja tudi najnovejše novice o Kriti Startfönstret ger nu också senaste nytt om Krita У початковому вікні програми ви можете бачити найсвіжіші новини щодо Krita xxThe startup window now also gives you the latest news about Kritaxx 它的启动画面可以向你展示 Krita 的最新官方新闻。 開始視窗也提供給您關於 Krita 的最新消息 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png There are over ten immensely powerful brush engines Hi ha més de deu motors de pinzell immensament potents Hi ha més de deu motors de pinzell immensament potents There are over ten immensely powerful brush engines Existen unos diez inmensamente potentes motores de pinceles Üle kümne ääretult võimeka pintslimootori Hamarretik gora isipu motor ikaragarri ahaltsu daude Il y a plus de 10 moteurs de brosse, tous extrêmement puissants Ci sono oltre dieci motori di pennelli incredibilmente potenti 10가지 종류의 강력한 브러시 엔진을 사용할 수 있습니다 Er zijn meer dan tien immens krachtige penseelengines Det finst meir enn ti enormt kraftige penselmotorar. Existem mais de dez motores de pincéis extremamente poderosos Mais de dez engines de pincéis incrivelmente poderosos disponíveis Existuje viac ako desať nesmierne výkonných štetcových enginov + Obstaja več kot deset izjemno močnih načinov podpor čopičev Det finns mer än tio enormt kraftfulla penselgränssnitt У програмі передбачено понад десяток надзвичайно потужних рушіїв пензлів xxThere are over ten immensely powerful brush enginesxx 它内建了超过十种功能强大的笔刷引擎。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png Create and use gamut masks to give your images a coherent feel Creeu i useu màscares de gamma per donar a les imatges una aparença coherent Creeu i useu màscares de gamma per donar a les imatges una aparença coherent Create and use gamut masks to give your images a coherent feel Cree y use gamas para proporcionar a sus imágenes un aspecto coherente Värviulatuse maskide loomine ja kasutamine piltidele kooskõlalise välimuse andmiseks Sortu eta erabili gama-maskarak zure irudiei izaera koherentea emateko Créer et utiiser les masques de Gamut pour donner à vos images une apparence cohérente Crea e utilizza maschere gamut per dare alle tue immagini un aspetto coerente 색역 마스크를 만들고 사용할 수 있습니다 Maak en gebruik gamut-maskers om uw afbeeldingen een coherent gevoel te geven Bruk fargeområde-masker for å gje bileta eit heilsleg uttrykk. Crie e use máscaras de gamute para dar às suas imagens uma aparência coerente Crie e use máscaras de gama para dar um senso de coerência às suas imagens Vytvorte a používajte gamutové masky, aby vašim obrázkom poskytli ucelený pocit + Ustvarite in uporabite maske gamut, da boste svojim slikam dali usklajeni občutek Att skapa och använda färgomfångsmasker ger bilder en sammanhängande känsla Створюйте маски палітри і користуйтеся ними для надання вашим малюнкам однорідного вигляду xxCreate and use gamut masks to give your images a coherent feelxx 它支持建立并使用色域蒙版,让图像的颜色选用更有条理。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png Into animation? Krita provides everything you need for traditional, hand-drawn animation Esteu amb animacions? El Krita proporciona tot el que cal per a l'animació a mà tradicional Esteu amb animacions? El Krita proporciona tot el que cal per a l'animació a mà tradicional Into animation? Krita provides everything you need for traditional, hand-drawn animation ¿Trabaja con animación? Krita proporciona todo lo necesario para la animación manual tradicional Sind huvitab animatsioon? Krita pakub kõike, mida läheb tarvis traditsioonilise käsitsi loodud animatsiooni jaoks Animaziorako? Krita-k ohiko eskuz-marraztutako animazioetarako behar duzun guztia dakar Vous réalisez des animations ? Krita vous fournit tout ce dont vous avez besoin pour l'animation traditionnelle et dessinée à la main Per le animazioni? Krita fornisce tutto ciò che ti server per l'animazione tradizionale, disegnata a mano 애니메이션을 만들 계획이 있으신가요? Krita를 통해서 수작업 애니메이션을 작업할 수 있습니다 Naar animatie? Krita biedt alles wat u nodig hebt voor traditionele, met de hand getekende animatie Interessert i animasjon? Krita har alt du treng for tradisjonelle, handteikna animasjonar. Gosta de animação? O Krita oferece tudo o que precisa para o desenho animado tradicional e desenhado à mão Curte animação? O Krita fornece tudo necessário para você poder trabalhar com animação tradicional ou feita à mão Ste do animácie? Krita poskytuje všetko, čo potrebujete pre tradičné ručne kreslené animácie + Za animacijo? Krita ponuja vse, kar potrebujete za tradicionalno, ročno narisano animacijo Gillar du animering? Krita tillhandahåller allt som behövs för traditionella, handritade animeringar Працюєте із анімацією? У Krita ви знайдете усе, що потрібно для створення традиційної, намальованої вручну анімації xxInto animation? Krita provides everything you need for traditional, hand-drawn animationxx Krita 还提供了手绘动画制作所需的全套工具和面板。 想做動畫?Krita 提供您在傳統、手繪動畫所需的任何東西 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png If you're new to digital painting, there's an extensive, up-to-date manual Si sou nou a la pintura digital, hi ha un manual extens i actualitzat Si sou nou a la pintura digital, hi ha un manual extens i actualitzat If you're new to digital painting, there's an extensive, up-to-date manual Si está empezando con el dibujo digital, dispone de un extenso y actualizado manual Kui oled digitaalkunstis alles uustulnuk, on meil välja pakkuda mahukas ajakohane käsiraamat Pintura digitalean berria bazara, gaurkotutako eskuliburu zabal bat dago Si vous êtes nouveau en art graphique, il y a un manuel à jour et très fourni Se sei nuovo del disegno digitale è disponibile un manuale completo e aggiornato 디지털 페인팅을 처음 시작하시거나, Krita의 기능을 더 알아 보려면 사용 설명서를 참조하십시오 Als u nieuw bent in digitaal schilderen, dan is er een uitgebreide, bijgewerkte handleiding. Viss du er nybegynnar innan digital teikning, finst det ei omfattande og oppdatert brukarhandbok. Se é novo na pintura digital, ou deseja saber mais sobre as possibilidades do Krita, existe um manual extenso e actualizado Se você for iniciante em pintura digital, há um extenso e atualizado manual Ak ste v oblasti digitálnej maľby nováčikom alebo sa chcete dozvedieť viac o možnostiach programu Krita, existuje o tom rozsiahla a aktuálna príručka + Če ste novi v digitalnem slikanju, obstaja obsežen, posodobljen priročnik Om digital målning är nytt för dig, finns en omfattande, aktuell handbok Якщо ви не маєте достатнього досвіду у цифровому малюванні, скористайтеся нашим докладним і актуальним підручником xxIf you're new to digital painting, there's an extensive, up-to-date manualxx 如果你是数字绘画的初学者,我们还准备了内容详尽,及时更新的使用手册。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png Graphics 2DGraphics RasterGraphics KDE krita org.kde.krita.desktop https://krita.org/en/item/krita-4-2-9-released/ https://download.kde.org/stable/krita/4.2.9/krita-4.2.9-x86_64.appimage 4b23574456338b4f5e2d7e6ba66ca5dce2d6924468fe6613bc468674ebec77d4 207515688 krita-4.2-9 https://download.kde.org/stable/krita/4.2.9/gmic_krita_qt-x86_64.appimage 5182b77ff35de7d9aa850ffbe6fd953b2eb7c20d39cf56302a938cab9c2497a0 32002088 gmic_krita_qt-4.2-9 https://download.kde.org/stable/krita/4.2.9/krita-4.2.9.dmg 34b606dcdbdf1c3702cebc924b9c11e1c9181ca6a9d5fa91e605bbfca554df9b 195113969 https://download.kde.org/stable/krita/4.2.9/krita-x64-4.2.9-setup.exe fd8345a4d4170c62e410f8cfe1547bb811eb312de953a540b3bf12bb79137b9f 110753224 https://download.kde.org/stable/krita/4.2.9/krita-x86-4.2.9-setup.exe c8efe945804ec9f08f019853c60895e390edfeb03bde13874b4d05231c03261d 110315944 https://download.kde.org/stable/krita/4.2.9/krita-4.2.9.tar.xz 4ef711887dd3ec5f2a1c42a80f2fd0fec1de0d4f3d0147b0efd418ac6e4d7567 170082028 https://krita.org/en/item/krita-4-2-8-released/ https://download.kde.org/stable/krita/4.2.8/krita-4.2.8-x86_64.appimage 4b23574456338b4f5e2d7e6ba66ca5dce2d6924468fe6613bc468674ebec77d4 207515688 krita-4.2-8 https://download.kde.org/stable/krita/4.2.8/gmic_krita_qt-x86_64.appimage e48ac43f86a22b7015ee2dc5ce4f35a72f22070150d926553ab0aafc8616a08f 32944104 gmic_krita_qt-4.2-8 https://download.kde.org/stable/krita/4.2.9/krita-4.2.9.dmg 34b606dcdbdf1c3702cebc924b9c11e1c9181ca6a9d5fa91e605bbfca554df9b 195113969 https://download.kde.org/stable/krita/4.2.8/krita-x64-4.2.8-setup.exe dca15dad13684622ae2704d77b34f0662bc109b7b6e3e7393a549b6418fcd419 109267000 https://download.kde.org/stable/krita/4.2.8/krita-x86-4.2.8-setup.exe 94fbdee4a923682fd64c0010b17ebd002e52de8f2ae239e611f3bda60d8abdee 107842760 https://download.kde.org/stable/krita/4.2.8/krita-4.2.8.2.tar.xz 1c3bb8a28ef8f7945e5f21f9ad87e01d8b831eea3487ff92742c930f3b7f744a 169994064 https://krita.org/en/item/krita-4-2-7-released/ https://download.kde.org/stable/krita/4.2.7.1/krita-4.2.7.1b-x86_64.appimage ddaeb8e02bad9d09fd3fc2d4ecf7ee677c3786cb13bb8e50eeaebf00e573f2d9 192188392 krita-4.2-7-1 https://download.kde.org/stable/krita/4.2.7.1/gmic_krita_qt-x86_64.appimage ea6e151399e850feb6e177ab52a40b32d9070888339f0c35c92ceff17816cae1 32968680 gmic_krita_qt-4-2-7-1 https://download.kde.org/stable/krita/4.2.7.1/krita-4.2.7.1.dmg eca62444e27ed51b177e75e9e674d726285e58483b41a37fa2b0d0ad2a8b34ba 173627205 https://download.kde.org/stable/krita/4.2.7.1/krita-x64-4.2.7.1-setup.exe 31538a959e7b271cf7c166f4e1e6162e5958e9b54055c4ecb1f50335fab2f01f 109190240 https://download.kde.org/stable/krita/4.2.8/krita-x86-4.2.8-setup.exe 3cb92f77e0913a20e2e02a0ba953460a7e39ad0e7a24b9530fc707f6808e8fec 107846008 https://www.microsoft.com/store/apps/9n6x57zgrw96
diff --git a/libs/image/layerstyles/KisLayerStyleKnockoutBlower.cpp b/libs/image/layerstyles/KisLayerStyleKnockoutBlower.cpp index 1583567a0a..48bae193b6 100644 --- a/libs/image/layerstyles/KisLayerStyleKnockoutBlower.cpp +++ b/libs/image/layerstyles/KisLayerStyleKnockoutBlower.cpp @@ -1,72 +1,82 @@ /* * Copyright (c) 2019 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 "KisLayerStyleKnockoutBlower.h" #include "kis_painter.h" #include "KoCompositeOpRegistry.h" +KisLayerStyleKnockoutBlower::KisLayerStyleKnockoutBlower() +{ +} + +KisLayerStyleKnockoutBlower::KisLayerStyleKnockoutBlower(const KisLayerStyleKnockoutBlower &rhs) + : m_knockoutSelection(rhs.m_knockoutSelection ? new KisSelection(*rhs.m_knockoutSelection) : nullptr) +{ +} + KisSelectionSP KisLayerStyleKnockoutBlower::knockoutSelectionLazy() { { QReadLocker l(&m_lock); if (m_knockoutSelection) { return m_knockoutSelection; } } { QWriteLocker l(&m_lock); if (m_knockoutSelection) { return m_knockoutSelection; } else { m_knockoutSelection = new KisSelection(new KisSelectionEmptyBounds(0)); return m_knockoutSelection; } } } void KisLayerStyleKnockoutBlower::setKnockoutSelection(KisSelectionSP selection) { QWriteLocker l(&m_lock); m_knockoutSelection = selection; } void KisLayerStyleKnockoutBlower::resetKnockoutSelection() { QWriteLocker l(&m_lock); m_knockoutSelection = 0; } void KisLayerStyleKnockoutBlower::apply(KisPainter *painter, KisPaintDeviceSP mergedStyle, const QRect &rect) const { QReadLocker l(&m_lock); KIS_SAFE_ASSERT_RECOVER_NOOP(m_knockoutSelection); painter->setOpacity(OPACITY_OPAQUE_U8); painter->setChannelFlags(QBitArray()); painter->setCompositeOp(COMPOSITE_COPY); painter->setSelection(m_knockoutSelection); painter->bitBlt(rect.topLeft(), mergedStyle, rect); + painter->setSelection(0); } bool KisLayerStyleKnockoutBlower::isEmpty() const { QReadLocker l(&m_lock); return !m_knockoutSelection; } diff --git a/libs/image/layerstyles/KisLayerStyleKnockoutBlower.h b/libs/image/layerstyles/KisLayerStyleKnockoutBlower.h index 50188eff42..d0555d1a7c 100644 --- a/libs/image/layerstyles/KisLayerStyleKnockoutBlower.h +++ b/libs/image/layerstyles/KisLayerStyleKnockoutBlower.h @@ -1,44 +1,47 @@ /* * Copyright (c) 2019 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 KISLAYERSTYLEKNOCKOUTBLOWER_H #define KISLAYERSTYLEKNOCKOUTBLOWER_H #include "kis_selection.h" #include class KisPainter; class KRITAIMAGE_EXPORT KisLayerStyleKnockoutBlower { public: + KisLayerStyleKnockoutBlower(); + KisLayerStyleKnockoutBlower(const KisLayerStyleKnockoutBlower &rhs); + KisSelectionSP knockoutSelectionLazy(); void setKnockoutSelection(KisSelectionSP selection); void resetKnockoutSelection(); void apply(KisPainter *painter, KisPaintDeviceSP mergedStyle, const QRect &rect) const; bool isEmpty() const; private: mutable QReadWriteLock m_lock; KisSelectionSP m_knockoutSelection; }; #endif // KISLAYERSTYLEKNOCKOUTBLOWER_H diff --git a/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp b/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp index 97797d7023..cd52f11502 100644 --- a/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp +++ b/libs/image/layerstyles/kis_layer_style_filter_projection_plane.cpp @@ -1,165 +1,166 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_style_filter_projection_plane.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_layer_style_filter.h" #include "kis_layer_style_filter_environment.h" #include "kis_psd_layer_style.h" #include "kis_painter.h" #include "kis_multiple_projection.h" #include "KisLayerStyleKnockoutBlower.h" struct KisLayerStyleFilterProjectionPlane::Private { Private(KisLayer *_sourceLayer) : sourceLayer(_sourceLayer), environment(new KisLayerStyleFilterEnvironment(_sourceLayer)) { KIS_SAFE_ASSERT_RECOVER_NOOP(_sourceLayer); } Private(const Private &rhs, KisLayer *_sourceLayer, KisPSDLayerStyleSP clonedStyle) : sourceLayer(_sourceLayer), filter(rhs.filter ? rhs.filter->clone() : 0), style(clonedStyle), environment(new KisLayerStyleFilterEnvironment(_sourceLayer)), + knockoutBlower(rhs.knockoutBlower), projection(rhs.projection) { KIS_SAFE_ASSERT_RECOVER_NOOP(_sourceLayer); } KisLayer *sourceLayer; QScopedPointer filter; KisPSDLayerStyleSP style; QScopedPointer environment; KisLayerStyleKnockoutBlower knockoutBlower; KisMultipleProjection projection; }; KisLayerStyleFilterProjectionPlane:: KisLayerStyleFilterProjectionPlane(KisLayer *sourceLayer) : m_d(new Private(sourceLayer)) { } KisLayerStyleFilterProjectionPlane::KisLayerStyleFilterProjectionPlane(const KisLayerStyleFilterProjectionPlane &rhs, KisLayer *sourceLayer, KisPSDLayerStyleSP clonedStyle) : m_d(new Private(*rhs.m_d, sourceLayer, clonedStyle)) { } KisLayerStyleFilterProjectionPlane::~KisLayerStyleFilterProjectionPlane() { } void KisLayerStyleFilterProjectionPlane::setStyle(KisLayerStyleFilter *filter, KisPSDLayerStyleSP style) { m_d->filter.reset(filter); m_d->style = style; } QRect KisLayerStyleFilterProjectionPlane::recalculate(const QRect& rect, KisNodeSP filthyNode) { Q_UNUSED(filthyNode); if (!m_d->sourceLayer || !m_d->filter) { warnKrita << "KisLayerStyleFilterProjectionPlane::recalculate(): [BUG] is not initialized"; return QRect(); } m_d->projection.clear(rect); m_d->filter->processDirectly(m_d->sourceLayer->projection(), &m_d->projection, &m_d->knockoutBlower, rect, m_d->style, m_d->environment.data()); return rect; } void KisLayerStyleFilterProjectionPlane::apply(KisPainter *painter, const QRect &rect) { m_d->projection.apply(painter->device(), rect, m_d->environment.data()); } KisPaintDeviceList KisLayerStyleFilterProjectionPlane::getLodCapableDevices() const { return m_d->projection.getLodCapableDevices(); } bool KisLayerStyleFilterProjectionPlane::isEmpty() const { return m_d->projection.isEmpty(); } KisLayerStyleKnockoutBlower *KisLayerStyleFilterProjectionPlane::knockoutBlower() const { return &m_d->knockoutBlower; } QRect KisLayerStyleFilterProjectionPlane::needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { if (!m_d->sourceLayer || !m_d->filter) { warnKrita << "KisLayerStyleFilterProjectionPlane::needRect(): [BUG] is not initialized"; return rect; } KIS_ASSERT_RECOVER_NOOP(pos == KisLayer::N_ABOVE_FILTHY); return m_d->filter->neededRect(rect, m_d->style, m_d->environment.data()); } QRect KisLayerStyleFilterProjectionPlane::changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { if (!m_d->sourceLayer || !m_d->filter) { warnKrita << "KisLayerStyleFilterProjectionPlane::changeRect(): [BUG] is not initialized"; return rect; } KIS_ASSERT_RECOVER_NOOP(pos == KisLayer::N_ABOVE_FILTHY); return m_d->filter->changedRect(rect, m_d->style, m_d->environment.data()); } QRect KisLayerStyleFilterProjectionPlane::accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { return needRect(rect, pos); } QRect KisLayerStyleFilterProjectionPlane::needRectForOriginal(const QRect &rect) const { return needRect(rect, KisLayer::N_ABOVE_FILTHY); } QRect KisLayerStyleFilterProjectionPlane::tightUserVisibleBounds() const { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->filter, QRect()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->sourceLayer, QRect()); return m_d->filter->changedRect(m_d->sourceLayer->exactBounds(), m_d->style, m_d->environment.data()); } diff --git a/libs/image/layerstyles/kis_layer_style_projection_plane.cpp b/libs/image/layerstyles/kis_layer_style_projection_plane.cpp index 48f3857b07..811e201ca6 100644 --- a/libs/image/layerstyles/kis_layer_style_projection_plane.cpp +++ b/libs/image/layerstyles/kis_layer_style_projection_plane.cpp @@ -1,426 +1,434 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_style_projection_plane.h" #include "kis_global.h" #include "kis_layer_style_filter_projection_plane.h" #include "kis_layer_projection_plane.h" #include "kis_psd_layer_style.h" #include "kis_ls_drop_shadow_filter.h" #include "kis_ls_satin_filter.h" #include "kis_ls_overlay_filter.h" #include "kis_ls_stroke_filter.h" #include "kis_ls_bevel_emboss_filter.h" #include "kis_projection_leaf.h" #include "kis_cached_paint_device.h" #include "kis_painter.h" #include "kis_ls_utils.h" #include "KisLayerStyleKnockoutBlower.h" struct Q_DECL_HIDDEN KisLayerStyleProjectionPlane::Private { KisLayerProjectionPlaneWSP sourceProjectionPlane; QVector stylesBefore; QVector stylesAfter; QVector stylesOverlay; KisCachedPaintDevice cachedPaintDevice; KisCachedSelection cachedSelection; KisLayer *sourceLayer = 0; KisPSDLayerStyleSP style; bool canHaveChildNodes = false; bool dependsOnLowerNodes = false; void initSourcePlane(KisLayer *sourceLayer) { KIS_SAFE_ASSERT_RECOVER_RETURN(sourceLayer); sourceProjectionPlane = sourceLayer->internalProjectionPlane(); canHaveChildNodes = sourceLayer->projectionLeaf()->canHaveChildLayers(); dependsOnLowerNodes = sourceLayer->projectionLeaf()->dependsOnLowerNodes(); this->sourceLayer = sourceLayer; } QVector allStyles() const { return stylesBefore + stylesOverlay + stylesAfter; } bool hasOverlayStyles() const { Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, stylesOverlay) { if (!plane->isEmpty()) return true; } return false; } bool hasKnockoutStyles() const { Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, stylesBefore) { if (!plane->knockoutBlower()->isEmpty()) return true; } Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, stylesAfter) { if (!plane->knockoutBlower()->isEmpty()) return true; } return false; } void applyComplexPlane(KisPainter *painter, KisLayerStyleFilterProjectionPlaneSP plane, const QRect &rect, KisPaintDeviceSP originalClone); }; KisLayerStyleProjectionPlane::KisLayerStyleProjectionPlane(KisLayer *sourceLayer) : m_d(new Private) { KisPSDLayerStyleSP style = sourceLayer->layerStyle(); KIS_ASSERT_RECOVER(style) { style = toQShared(new KisPSDLayerStyle()); } init(sourceLayer, style); } KisLayerStyleProjectionPlane::KisLayerStyleProjectionPlane(const KisLayerStyleProjectionPlane &rhs, KisLayer *sourceLayer, KisPSDLayerStyleSP clonedStyle) : m_d(new Private) { m_d->initSourcePlane(sourceLayer); m_d->style = clonedStyle; KIS_SAFE_ASSERT_RECOVER(m_d->style) { m_d->style = toQShared(new KisPSDLayerStyle()); } - Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, rhs.m_d->allStyles()) { + Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, rhs.m_d->stylesBefore) { m_d->stylesBefore << toQShared(new KisLayerStyleFilterProjectionPlane(*plane, sourceLayer, m_d->style)); } + + Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, rhs.m_d->stylesAfter) { + m_d->stylesAfter << toQShared(new KisLayerStyleFilterProjectionPlane(*plane, sourceLayer, m_d->style)); + } + + Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, rhs.m_d->stylesOverlay) { + m_d->stylesOverlay << toQShared(new KisLayerStyleFilterProjectionPlane(*plane, sourceLayer, m_d->style)); + } } // for testing purposes only! KisLayerStyleProjectionPlane::KisLayerStyleProjectionPlane(KisLayer *sourceLayer, KisPSDLayerStyleSP layerStyle) : m_d(new Private) { init(sourceLayer, layerStyle); } void KisLayerStyleProjectionPlane::init(KisLayer *sourceLayer, KisPSDLayerStyleSP style) { KIS_SAFE_ASSERT_RECOVER_RETURN(sourceLayer); m_d->initSourcePlane(sourceLayer); m_d->style = style; { KisLayerStyleFilterProjectionPlane *dropShadow = new KisLayerStyleFilterProjectionPlane(sourceLayer); dropShadow->setStyle(new KisLsDropShadowFilter(KisLsDropShadowFilter::DropShadow), style); m_d->stylesBefore << toQShared(dropShadow); } { KisLayerStyleFilterProjectionPlane *outerGlow = new KisLayerStyleFilterProjectionPlane(sourceLayer); outerGlow->setStyle(new KisLsDropShadowFilter(KisLsDropShadowFilter::OuterGlow), style); m_d->stylesAfter << toQShared(outerGlow); } { KisLayerStyleFilterProjectionPlane *stroke = new KisLayerStyleFilterProjectionPlane(sourceLayer); stroke->setStyle(new KisLsStrokeFilter(), style); m_d->stylesAfter << toQShared(stroke); } { KisLayerStyleFilterProjectionPlane *bevelEmboss = new KisLayerStyleFilterProjectionPlane(sourceLayer); bevelEmboss->setStyle(new KisLsBevelEmbossFilter(), style); m_d->stylesAfter << toQShared(bevelEmboss); } { KisLayerStyleFilterProjectionPlane *patternOverlay = new KisLayerStyleFilterProjectionPlane(sourceLayer); patternOverlay->setStyle(new KisLsOverlayFilter(KisLsOverlayFilter::Pattern), style); m_d->stylesOverlay << toQShared(patternOverlay); } { KisLayerStyleFilterProjectionPlane *gradientOverlay = new KisLayerStyleFilterProjectionPlane(sourceLayer); gradientOverlay->setStyle(new KisLsOverlayFilter(KisLsOverlayFilter::Gradient), style); m_d->stylesOverlay << toQShared(gradientOverlay); } { KisLayerStyleFilterProjectionPlane *colorOverlay = new KisLayerStyleFilterProjectionPlane(sourceLayer); colorOverlay->setStyle(new KisLsOverlayFilter(KisLsOverlayFilter::Color), style); m_d->stylesOverlay << toQShared(colorOverlay); } { KisLayerStyleFilterProjectionPlane *satin = new KisLayerStyleFilterProjectionPlane(sourceLayer); satin->setStyle(new KisLsSatinFilter(), style); m_d->stylesOverlay << toQShared(satin); } { KisLayerStyleFilterProjectionPlane *innerGlow = new KisLayerStyleFilterProjectionPlane(sourceLayer); innerGlow->setStyle(new KisLsDropShadowFilter(KisLsDropShadowFilter::InnerGlow), style); m_d->stylesOverlay << toQShared(innerGlow); } { KisLayerStyleFilterProjectionPlane *innerShadow = new KisLayerStyleFilterProjectionPlane(sourceLayer); innerShadow->setStyle(new KisLsDropShadowFilter(KisLsDropShadowFilter::InnerShadow), style); m_d->stylesOverlay << toQShared(innerShadow); } } KisLayerStyleProjectionPlane::~KisLayerStyleProjectionPlane() { } KisAbstractProjectionPlaneSP KisLayerStyleProjectionPlane::factoryObject(KisLayer *sourceLayer) { Q_ASSERT(sourceLayer); return toQShared(new KisLayerStyleProjectionPlane(sourceLayer)); } QRect KisLayerStyleProjectionPlane::recalculate(const QRect& rect, KisNodeSP filthyNode) { KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); QRect result = rect; if (m_d->style->isEnabled()) { result = sourcePlane->recalculate(stylesNeedRect(rect), filthyNode); Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { plane->recalculate(rect, filthyNode); } } else { result = sourcePlane->recalculate(rect, filthyNode); } return result; } void KisLayerStyleProjectionPlane::Private::applyComplexPlane(KisPainter *painter, KisLayerStyleFilterProjectionPlaneSP plane, const QRect &rect, KisPaintDeviceSP originalClone) { if (plane->isEmpty()) return; if (!plane->knockoutBlower()->isEmpty()) { KisCachedPaintDevice::Guard d1(originalClone, cachedPaintDevice); KisPaintDeviceSP mergedStyle = d1.device(); mergedStyle->makeCloneFromRough(originalClone, rect); KisPainter overlayPainter(mergedStyle); plane->apply(&overlayPainter, rect); plane->knockoutBlower()->apply(painter, mergedStyle, rect); } else { plane->apply(painter, rect); } } void KisLayerStyleProjectionPlane::apply(KisPainter *painter, const QRect &rect) { KisLayerProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); if (m_d->style->isEnabled()) { if (m_d->hasOverlayStyles() || m_d->hasKnockoutStyles()) { KisCachedPaintDevice::Guard d1(painter->device(), m_d->cachedPaintDevice); KisPaintDeviceSP originalClone = d1.device(); originalClone->makeCloneFromRough(painter->device(), rect); Q_FOREACH (const KisLayerStyleFilterProjectionPlaneSP plane, m_d->stylesBefore) { m_d->applyComplexPlane(painter, plane, rect, originalClone); } if (m_d->hasOverlayStyles()) { KisCachedSelection::Guard s1(m_d->cachedSelection); KisSelectionSP knockoutSelection = s1.selection(); KisLsUtils::selectionFromAlphaChannel(m_d->sourceLayer->projection(), knockoutSelection, rect); KisCachedPaintDevice::Guard d2(painter->device(), m_d->cachedPaintDevice); KisPaintDeviceSP sourceProjection = d2.device(); sourceProjection->makeCloneFromRough(painter->device(), rect); { KisPainter overlayPainter(sourceProjection); sourcePlane->applyMaxOutAlpha(&overlayPainter, rect); Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesOverlay) { plane->apply(&overlayPainter, rect); } } KisLayerStyleKnockoutBlower blower; blower.setKnockoutSelection(knockoutSelection); blower.apply(painter, sourceProjection, rect); blower.resetKnockoutSelection(); } else { sourcePlane->apply(painter, rect); } Q_FOREACH (KisLayerStyleFilterProjectionPlaneSP plane, m_d->stylesAfter) { m_d->applyComplexPlane(painter, plane, rect, originalClone); } } else { Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesBefore) { plane->apply(painter, rect); } sourcePlane->apply(painter, rect); Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->stylesAfter) { plane->apply(painter, rect); } } } else { sourcePlane->apply(painter, rect); } } KisPaintDeviceList KisLayerStyleProjectionPlane::getLodCapableDevices() const { KisPaintDeviceList list; KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); if (m_d->style->isEnabled()) { Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { list << plane->getLodCapableDevices(); } list << sourcePlane->getLodCapableDevices(); } else { list << sourcePlane->getLodCapableDevices(); } return list; } QRect KisLayerStyleProjectionPlane::needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { /** * Need rect should also be adjust for the layers that generate their 'original' * based on the contents of the underlying layers like KisAdjustmentLayer * * \see bug 390299 */ QRect needRect = rect; const bool adjustmentAboveDirty = m_d->dependsOnLowerNodes && (pos & KisLayer::N_FILTHY || pos & KisLayer::N_ABOVE_FILTHY); if (m_d->style->isEnabled() && adjustmentAboveDirty) { needRect |= stylesNeedRect(rect); } KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); needRect = sourcePlane->needRect(needRect, pos); return needRect; } QRect KisLayerStyleProjectionPlane::changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); QRect layerChangeRect = sourcePlane->changeRect(rect, pos); QRect changeRect = layerChangeRect; if (m_d->style->isEnabled()) { Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { changeRect |= plane->changeRect(layerChangeRect, KisLayer::N_ABOVE_FILTHY); } } return changeRect; } QRect KisLayerStyleProjectionPlane::accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); QRect accessRect = sourcePlane->accessRect(rect, pos); if (m_d->style->isEnabled()) { Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { accessRect |= plane->accessRect(rect, KisLayer::N_ABOVE_FILTHY); } } return accessRect; } QRect KisLayerStyleProjectionPlane::needRectForOriginal(const QRect &rect) const { /** * Need rect should also be adjust for the layers that generate their 'original' * based on the contents of the child layers like KisGroupLayer * * \see bug 366419 */ QRect needRect = rect; if (m_d->style->isEnabled()) { needRect |= stylesNeedRect(needRect); } KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); needRect = sourcePlane->needRectForOriginal(needRect); return needRect; } QRect KisLayerStyleProjectionPlane::tightUserVisibleBounds() const { KisAbstractProjectionPlaneSP sourcePlane = m_d->sourceProjectionPlane.toStrongRef(); QRect rect = sourcePlane->tightUserVisibleBounds(); Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { rect |= plane->tightUserVisibleBounds(); } return rect; } QRect KisLayerStyleProjectionPlane::stylesNeedRect(const QRect &rect) const { QRect needRect = rect; Q_FOREACH (const KisAbstractProjectionPlaneSP plane, m_d->allStyles()) { needRect |= plane->needRect(rect, KisLayer::N_ABOVE_FILTHY); } return needRect; } diff --git a/libs/image/layerstyles/kis_ls_stroke_filter.cpp b/libs/image/layerstyles/kis_ls_stroke_filter.cpp index 548e593f08..d0f8b939c8 100644 --- a/libs/image/layerstyles/kis_ls_stroke_filter.cpp +++ b/libs/image/layerstyles/kis_ls_stroke_filter.cpp @@ -1,170 +1,171 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_ls_stroke_filter.h" #include #include #include #include #include "psd.h" #include "kis_convolution_kernel.h" #include "kis_convolution_painter.h" #include "kis_gaussian_kernel.h" #include "kis_pixel_selection.h" #include "kis_fill_painter.h" #include "kis_gradient_painter.h" #include "kis_iterator_ng.h" #include "kis_random_accessor_ng.h" #include "kis_psd_layer_style.h" #include "kis_layer_style_filter_environment.h" #include "kis_ls_utils.h" #include "kis_multiple_projection.h" #include "kis_cached_paint_device.h" #include "krita_utils.h" #include "KisLayerStyleKnockoutBlower.h" namespace { int borderSize(psd_stroke_position position, int size) { int border = 0; switch (position) { case psd_stroke_outside: border = size + 1; break; case psd_stroke_center: border = qCeil(0.5 * size) + 1; break; case psd_stroke_inside: border = 1; break; } return border; } } KisLsStrokeFilter::KisLsStrokeFilter() : KisLayerStyleFilter(KoID("lsstroke", i18n("Stroke (style)"))) { } KisLsStrokeFilter::KisLsStrokeFilter(const KisLsStrokeFilter &rhs) : KisLayerStyleFilter(rhs) { } KisLayerStyleFilter *KisLsStrokeFilter::clone() const { return new KisLsStrokeFilter(*this); } void KisLsStrokeFilter::applyStroke(KisPaintDeviceSP srcDevice, KisMultipleProjection *dst, KisLayerStyleKnockoutBlower *blower, const QRect &applyRect, const psd_layer_effects_stroke *config, KisLayerStyleFilterEnvironment *env) const { if (applyRect.isEmpty()) return; const QRect needRect = kisGrowRect(applyRect, borderSize(config->position(), config->size())); - KisSelectionSP finalBlowerSelection = blower->knockoutSelectionLazy(); + KisSelectionSP baseSelection = blower->knockoutSelectionLazy(); + KisPixelSelectionSP selection = baseSelection->pixelSelection(); KisCachedSelection::Guard s1(*env->cachedSelection()); - KisSelectionSP baseSelection = s1.selection(); - - KisLsUtils::selectionFromAlphaChannel(srcDevice, baseSelection, needRect); - KisPixelSelectionSP selection = baseSelection->pixelSelection(); + KisPixelSelectionSP dilatedSelection = s1.selection()->pixelSelection(); + KisLsUtils::selectionFromAlphaChannel(srcDevice, s1.selection(), needRect); { KisCachedSelection::Guard s2(*env->cachedSelection()); - KisPixelSelectionSP knockOutSelection = s2.selection()->pixelSelection(); - knockOutSelection->makeCloneFromRough(selection, needRect); + KisPixelSelectionSP erodedSelection = s2.selection()->pixelSelection(); + erodedSelection->makeCloneFromRough(dilatedSelection, needRect); if (config->position() == psd_stroke_outside) { - KisGaussianKernel::applyDilate(selection, needRect, config->size(), QBitArray(), 0, true); + KisGaussianKernel::applyDilate(dilatedSelection, needRect, config->size(), QBitArray(), 0, true); } else if (config->position() == psd_stroke_inside) { - KisGaussianKernel::applyErodeU8(knockOutSelection, needRect, config->size(), QBitArray(), 0, true); + KisGaussianKernel::applyErodeU8(erodedSelection, needRect, config->size(), QBitArray(), 0, true); } else if (config->position() == psd_stroke_center) { - KisGaussianKernel::applyDilate(selection, needRect, 0.5 * config->size(), QBitArray(), 0, true); - KisGaussianKernel::applyErodeU8(knockOutSelection, needRect, 0.5 * config->size(), QBitArray(), 0, true); + KisGaussianKernel::applyDilate(dilatedSelection, needRect, 0.5 * config->size(), QBitArray(), 0, true); + KisGaussianKernel::applyErodeU8(erodedSelection, needRect, 0.5 * config->size(), QBitArray(), 0, true); } KisPainter gc(selection); + + gc.setCompositeOp(COMPOSITE_COPY); + gc.bitBlt(applyRect.topLeft(), dilatedSelection, applyRect); + gc.setCompositeOp(COMPOSITE_ERASE); - gc.bitBlt(applyRect.topLeft(), knockOutSelection, applyRect); + gc.bitBlt(applyRect.topLeft(), erodedSelection, applyRect); gc.end(); - - KisPainter::copyAreaOptimized(applyRect.topLeft(), selection, finalBlowerSelection->pixelSelection(), applyRect); } const QString compositeOp = config->blendMode(); const quint8 opacityU8 = quint8(qRound(255.0 / 100.0 * config->opacity())); KisPaintDeviceSP dstDevice = dst->getProjection(KisMultipleProjection::defaultProjectionId(), compositeOp, opacityU8, QBitArray(), srcDevice); KisLsUtils::fillOverlayDevice(dstDevice, applyRect, config, env); } void KisLsStrokeFilter::processDirectly(KisPaintDeviceSP src, KisMultipleProjection *dst, KisLayerStyleKnockoutBlower *blower, const QRect &applyRect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { Q_UNUSED(env); KIS_ASSERT_RECOVER_RETURN(style); const psd_layer_effects_stroke *config = style->stroke(); if (!KisLsUtils::checkEffectEnabled(config, dst)) return; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); applyStroke(src, dst, blower, applyRect, w.config, env); } QRect KisLsStrokeFilter::neededRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { const psd_layer_effects_stroke *config = style->stroke(); if (!config->effectEnabled()) return rect; KisLsUtils::LodWrapper w(env->currentLevelOfDetail(), config); return kisGrowRect(rect, borderSize(w.config->position(), w.config->size())); } QRect KisLsStrokeFilter::changedRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const { return neededRect(rect, style, env); } diff --git a/libs/image/tests/kis_layer_style_projection_plane_test.cpp b/libs/image/tests/kis_layer_style_projection_plane_test.cpp index 13009ef7bf..430e2f6c1a 100644 --- a/libs/image/tests/kis_layer_style_projection_plane_test.cpp +++ b/libs/image/tests/kis_layer_style_projection_plane_test.cpp @@ -1,617 +1,653 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_style_projection_plane_test.h" #include #include "testutil.h" #include #include #include #include #include "kis_transparency_mask.h" #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_pixel_selection.h" #include "layerstyles/kis_layer_style_projection_plane.h" #include "kis_psd_layer_style.h" #include "kis_paint_device_debug_utils.h" #include void KisLayerStyleProjectionPlaneTest::test(KisPSDLayerStyleSP style, const QString testName) { const QRect imageRect(0, 0, 200, 200); const QRect rFillRect(10, 10, 100, 100); const QRect tMaskRect(50, 50, 20, 20); const QRect partialSelectionRect(90, 50, 20, 20); const QRect updateRect1(10, 10, 50, 100); const QRect updateRect2(60, 10, 50, 100); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, imageRect.width(), imageRect.height(), cs, "styles test"); KisPaintLayerSP layer = new KisPaintLayer(image, "test", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerStyleProjectionPlane plane(layer.data(), style); KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "00L_initial", testName); //layer->paintDevice()->fill(rFillRect, KoColor(Qt::red, cs)); { KisPainter gc(layer->paintDevice()); gc.setPaintColor(KoColor(Qt::red, cs)); gc.setFillStyle(KisPainter::FillStyleForegroundColor); gc.paintEllipse(rFillRect); } KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "01L_fill", testName); KisPaintDeviceSP projection = new KisPaintDevice(cs); { + projection->clear(); const QRect changeRect = plane.changeRect(rFillRect, KisLayer::N_FILTHY); dbgKrita << ppVar(rFillRect) << ppVar(changeRect); plane.recalculate(changeRect, layer); KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "02L_recalculate_fill", testName); KisPainter painter(projection); plane.apply(&painter, changeRect); KIS_DUMP_DEVICE_2(projection, imageRect, "03P_apply_on_fill", testName); } + { + KisNodeSP clonedNode = layer->clone(); + KisLayerSP clonedLayer = dynamic_cast(layer.data()); + + KisLayerStyleProjectionPlaneSP clonedPlane = + toQShared(new KisLayerStyleProjectionPlane(plane, + clonedLayer.data(), + style)); + + const QRect changeRect = clonedPlane->changeRect(rFillRect, KisLayer::N_FILTHY); + dbgKrita << ppVar(rFillRect) << ppVar(changeRect); + + KIS_DUMP_DEVICE_2(clonedLayer->projection(), imageRect, "04L_clone_state_after_copy", testName); + + { + projection->clear(); + KisPainter painter(projection); + clonedPlane->apply(&painter, changeRect); + + KIS_DUMP_DEVICE_2(projection, imageRect, "05P_apply_clone_after_copy", testName); + } + + clonedPlane->recalculate(changeRect, clonedLayer); + + KIS_DUMP_DEVICE_2(clonedLayer->projection(), imageRect, "06L_recalculate_clone", testName); + + { + projection->clear(); + KisPainter painter(projection); + clonedPlane->apply(&painter, changeRect); + + KIS_DUMP_DEVICE_2(projection, imageRect, "07P_apply_recalculated_clone", testName); + } + } + //return; KisTransparencyMaskSP transparencyMask = new KisTransparencyMask(); KisSelectionSP selection = new KisSelection(); selection->pixelSelection()->select(tMaskRect, OPACITY_OPAQUE_U8); transparencyMask->setSelection(selection); image->addNode(transparencyMask, layer); - KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "04L_mask_added", testName); + KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "08L_mask_added", testName); plane.recalculate(imageRect, layer); - KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "05L_mask_added_recalculated", testName); + KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "09_mask_added_recalculated", testName); { projection->clear(); KisPainter painter(projection); plane.apply(&painter, imageRect); - KIS_DUMP_DEVICE_2(projection, imageRect, "06P_apply_on_mask", testName); + KIS_DUMP_DEVICE_2(projection, imageRect, "10P_apply_on_mask", testName); } selection->pixelSelection()->select(partialSelectionRect, OPACITY_OPAQUE_U8); { const QRect changeRect = plane.changeRect(partialSelectionRect, KisLayer::N_FILTHY); projection->clear(changeRect); dbgKrita << ppVar(partialSelectionRect) << ppVar(changeRect); plane.recalculate(changeRect, layer); - KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "07L_recalculate_partial", testName); + KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "11L_recalculate_partial", testName); KisPainter painter(projection); plane.apply(&painter, changeRect); - KIS_DUMP_DEVICE_2(projection, imageRect, "08P_apply_partial", testName); + KIS_DUMP_DEVICE_2(projection, imageRect, "12P_apply_partial", testName); } // half updates transparencyMask->setVisible(false); { const QRect changeRect = plane.changeRect(updateRect1, KisLayer::N_FILTHY); projection->clear(changeRect); dbgKrita << ppVar(updateRect1) << ppVar(changeRect); plane.recalculate(changeRect, layer); - KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "09L_recalculate_half1", testName); + KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "13recalculate_half1", testName); KisPainter painter(projection); plane.apply(&painter, changeRect); - KIS_DUMP_DEVICE_2(projection, imageRect, "10P_apply_half1", testName); + KIS_DUMP_DEVICE_2(projection, imageRect, "14P_apply_half1", testName); } { const QRect changeRect = plane.changeRect(updateRect2, KisLayer::N_FILTHY); projection->clear(changeRect); dbgKrita << ppVar(updateRect2) << ppVar(changeRect); plane.recalculate(changeRect, layer); - KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "09L_recalculate_half1", testName); + KIS_DUMP_DEVICE_2(layer->projection(), imageRect, "15L_recalculate_half1", testName); KisPainter painter(projection); plane.apply(&painter, changeRect); - KIS_DUMP_DEVICE_2(projection, imageRect, "10P_apply_half2", testName); + KIS_DUMP_DEVICE_2(projection, imageRect, "16P_apply_half2", testName); } } void KisLayerStyleProjectionPlaneTest::testShadow() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setSize(15); style->dropShadow()->setDistance(15); style->dropShadow()->setOpacity(70); style->dropShadow()->setNoise(30); style->dropShadow()->setEffectEnabled(true); style->innerShadow()->setSize(10); style->innerShadow()->setSpread(10); style->innerShadow()->setDistance(5); style->innerShadow()->setOpacity(70); style->innerShadow()->setNoise(30); style->innerShadow()->setEffectEnabled(true); test(style, "shadow"); } void KisLayerStyleProjectionPlaneTest::testGlow() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->outerGlow()->setSize(15); style->outerGlow()->setSpread(10); style->outerGlow()->setOpacity(70); style->outerGlow()->setNoise(30); style->outerGlow()->setEffectEnabled(true); style->outerGlow()->setColor(Qt::green); test(style, "glow_outer"); } #include void KisLayerStyleProjectionPlaneTest::testGlowGradient() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->outerGlow()->setSize(15); style->outerGlow()->setSpread(10); style->outerGlow()->setOpacity(70); style->outerGlow()->setNoise(10); style->outerGlow()->setEffectEnabled(true); style->outerGlow()->setColor(Qt::green); QLinearGradient testGradient; testGradient.setColorAt(0.0, Qt::white); testGradient.setColorAt(0.5, Qt::green); testGradient.setColorAt(1.0, Qt::black); testGradient.setSpread(QGradient::ReflectSpread); QSharedPointer gradient( KoStopGradient::fromQGradient(&testGradient)); style->outerGlow()->setGradient(gradient); style->outerGlow()->setFillType(psd_fill_gradient); test(style, "glow_outer_grad"); } void KisLayerStyleProjectionPlaneTest::testGlowGradientJitter() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->outerGlow()->setSize(15); style->outerGlow()->setSpread(10); style->outerGlow()->setOpacity(70); style->outerGlow()->setNoise(0); style->outerGlow()->setEffectEnabled(true); style->outerGlow()->setColor(Qt::green); QLinearGradient testGradient; testGradient.setColorAt(0.0, Qt::white); testGradient.setColorAt(0.5, Qt::green); testGradient.setColorAt(1.0, Qt::black); testGradient.setSpread(QGradient::ReflectSpread); QSharedPointer gradient( KoStopGradient::fromQGradient(&testGradient)); style->outerGlow()->setGradient(gradient); style->outerGlow()->setFillType(psd_fill_gradient); style->outerGlow()->setJitter(20); test(style, "glow_outer_grad_jit"); } void KisLayerStyleProjectionPlaneTest::testGlowInnerGradient() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->innerGlow()->setSize(15); style->innerGlow()->setSpread(10); style->innerGlow()->setOpacity(80); style->innerGlow()->setNoise(10); style->innerGlow()->setEffectEnabled(true); style->innerGlow()->setColor(Qt::white); QLinearGradient testGradient; testGradient.setColorAt(0.0, Qt::white); testGradient.setColorAt(0.5, Qt::green); testGradient.setColorAt(1.0, Qt::black); testGradient.setSpread(QGradient::ReflectSpread); QSharedPointer gradient( KoStopGradient::fromQGradient(&testGradient)); style->innerGlow()->setGradient(gradient); style->innerGlow()->setFillType(psd_fill_gradient); test(style, "glow_inner_grad"); style->innerGlow()->setFillType(psd_fill_solid_color); style->innerGlow()->setSource(psd_glow_center); test(style, "glow_inner_grad_center"); } #include void KisLayerStyleProjectionPlaneTest::testSatin() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->satin()->setSize(15); style->satin()->setOpacity(80); style->satin()->setAngle(180); style->satin()->setEffectEnabled(true); style->satin()->setColor(Qt::white); style->satin()->setBlendMode(COMPOSITE_LINEAR_DODGE); test(style, "satin"); } void KisLayerStyleProjectionPlaneTest::testColorOverlay() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->colorOverlay()->setOpacity(80); style->colorOverlay()->setEffectEnabled(true); style->colorOverlay()->setColor(Qt::white); style->colorOverlay()->setBlendMode(COMPOSITE_LINEAR_DODGE); test(style, "color_overlay"); } void KisLayerStyleProjectionPlaneTest::testGradientOverlay() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->gradientOverlay()->setAngle(90); style->gradientOverlay()->setOpacity(80); style->gradientOverlay()->setEffectEnabled(true); style->gradientOverlay()->setBlendMode(COMPOSITE_LINEAR_DODGE); style->gradientOverlay()->setAlignWithLayer(true); style->gradientOverlay()->setScale(100); style->gradientOverlay()->setStyle(psd_gradient_style_diamond); QLinearGradient testGradient; testGradient.setColorAt(0.0, Qt::white); testGradient.setColorAt(0.5, Qt::green); testGradient.setColorAt(1.0, Qt::black); testGradient.setSpread(QGradient::ReflectSpread); QSharedPointer gradient( KoStopGradient::fromQGradient(&testGradient)); style->gradientOverlay()->setGradient(gradient); test(style, "grad_overlay"); } void KisLayerStyleProjectionPlaneTest::testPatternOverlay() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->patternOverlay()->setOpacity(80); style->patternOverlay()->setEffectEnabled(true); style->patternOverlay()->setBlendMode(COMPOSITE_LINEAR_DODGE); style->patternOverlay()->setScale(100); style->patternOverlay()->setAlignWithLayer(false); QString fileName(TestUtil::fetchDataFileLazy("pattern.pat")); KoPatternSP pattern(new KoPattern(fileName)); QVERIFY(pattern->load(KisGlobalResourcesInterface::instance())); style->patternOverlay()->setPattern(pattern); test(style, "pat_overlay"); } void KisLayerStyleProjectionPlaneTest::testStroke() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->stroke()->setColor(Qt::blue); style->stroke()->setOpacity(80); style->stroke()->setEffectEnabled(true); style->stroke()->setBlendMode(COMPOSITE_OVER); style->stroke()->setSize(3); style->stroke()->setPosition(psd_stroke_center); test(style, "stroke_col_ctr"); style->stroke()->setPosition(psd_stroke_outside); test(style, "stroke_col_out"); style->stroke()->setPosition(psd_stroke_inside); test(style, "stroke_col_in"); QString fileName(TestUtil::fetchDataFileLazy("pattern.pat")); KoPatternSP pattern(new KoPattern(fileName)); QVERIFY(pattern->load(KisGlobalResourcesInterface::instance())); style->stroke()->setPattern(pattern); style->stroke()->setFillType(psd_fill_pattern); test(style, "stroke_pat"); QLinearGradient testGradient; testGradient.setColorAt(0.0, Qt::white); testGradient.setColorAt(0.5, Qt::green); testGradient.setColorAt(1.0, Qt::black); testGradient.setSpread(QGradient::ReflectSpread); QSharedPointer gradient( KoStopGradient::fromQGradient(&testGradient)); style->stroke()->setGradient(gradient); style->stroke()->setFillType(psd_fill_gradient); test(style, "stroke_grad"); } #include "layerstyles/gimp_bump_map.h" void KisLayerStyleProjectionPlaneTest::testBumpmap() { KisPixelSelectionSP device = new KisPixelSelection(); const int numCycles = 30; const int step = 5; QRect applyRect(200, 100, 100, 100); QRect fillRect(210, 110, 80, 80); quint8 selectedness = 256 - numCycles * step; for (int i = 0; i < numCycles; i++) { device->select(fillRect, selectedness); fillRect = kisGrowRect(fillRect, -1); selectedness += step; } KIS_DUMP_DEVICE_2(device, applyRect, "00_initial", "bumpmap"); bumpmap_vals_t bmvals; bmvals.azimuth = 240; bmvals.elevation = 30; bmvals.depth = 50; bmvals.ambient = 128; bmvals.compensate = false; bmvals.invert = false; bmvals.type = 0; bumpmap(device, applyRect, bmvals); KIS_DUMP_DEVICE_2(device, applyRect, "01_bumpmapped", "bumpmap"); } void KisLayerStyleProjectionPlaneTest::testBevel() { KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->bevelAndEmboss()->setEffectEnabled(true); style->bevelAndEmboss()->setAngle(135); style->bevelAndEmboss()->setAltitude(45); style->bevelAndEmboss()->setDepth(100); style->bevelAndEmboss()->setHighlightColor(Qt::white); style->bevelAndEmboss()->setHighlightBlendMode(COMPOSITE_OVER); style->bevelAndEmboss()->setHighlightOpacity(100); style->bevelAndEmboss()->setShadowColor(Qt::black); style->bevelAndEmboss()->setShadowBlendMode(COMPOSITE_OVER); style->bevelAndEmboss()->setShadowOpacity(100); QString fileName(TestUtil::fetchDataFileLazy("pattern.pat")); KoPatternSP pattern(new KoPattern(fileName)); QVERIFY(pattern->load(KisGlobalResourcesInterface::instance())); style->bevelAndEmboss()->setTexturePattern(pattern); style->bevelAndEmboss()->setTextureEnabled(true); style->bevelAndEmboss()->setTextureDepth(-10); style->bevelAndEmboss()->setTextureInvert(false); style->bevelAndEmboss()->setStyle(psd_bevel_outer_bevel); style->bevelAndEmboss()->setDirection(psd_direction_up); style->bevelAndEmboss()->setSoften(0); test(style, "bevel_outer_up"); style->bevelAndEmboss()->setTextureInvert(true); style->bevelAndEmboss()->setStyle(psd_bevel_outer_bevel); style->bevelAndEmboss()->setDirection(psd_direction_up); style->bevelAndEmboss()->setSoften(0); test(style, "bevel_outer_up_invert_texture"); style->bevelAndEmboss()->setTextureInvert(false); style->bevelAndEmboss()->setStyle(psd_bevel_outer_bevel); style->bevelAndEmboss()->setDirection(psd_direction_down); style->bevelAndEmboss()->setSoften(0); test(style, "bevel_outer_down"); style->bevelAndEmboss()->setStyle(psd_bevel_emboss); style->bevelAndEmboss()->setDirection(psd_direction_up); style->bevelAndEmboss()->setSoften(0); test(style, "bevel_emboss_up"); style->bevelAndEmboss()->setStyle(psd_bevel_pillow_emboss); style->bevelAndEmboss()->setDirection(psd_direction_up); style->bevelAndEmboss()->setSoften(0); test(style, "bevel_pillow_up"); style->bevelAndEmboss()->setStyle(psd_bevel_pillow_emboss); style->bevelAndEmboss()->setDirection(psd_direction_down); style->bevelAndEmboss()->setSoften(0); test(style, "bevel_pillow_down"); style->bevelAndEmboss()->setStyle(psd_bevel_pillow_emboss); style->bevelAndEmboss()->setDirection(psd_direction_up); style->bevelAndEmboss()->setSoften(3); test(style, "bevel_pillow_up_soft"); } #include "kis_ls_utils.h" void KisLayerStyleProjectionPlaneTest::testBlending() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP layer = new KisPaintDevice(cs); KisPaintDeviceSP overlay = new KisPaintDevice(cs); KisPaintDeviceSP bg = new KisPaintDevice(cs); KisPaintDeviceSP result = new KisPaintDevice(cs); const int width = 20; KoColor color(Qt::transparent, cs); QVector layerColors; QVector overlayColors; QVector bgColors; layerColors << QColor(0, 255, 0); layerColors << QColor(128, 255, 64); overlayColors << QColor(255, 0, 0); overlayColors << QColor(255, 128, 64); bgColors << QColor(0, 0, 0, 0); bgColors << QColor(0, 0, 0, 255); bgColors << QColor(255, 255, 255, 255); bgColors << QColor(64, 128, 255, 255); bgColors << QColor(0, 0, 0, 128); bgColors << QColor(255, 255, 255, 128); bgColors << QColor(64, 128, 255, 128); const int overlayOpacity = 255; const int layerOpacity = 255; int y = 1; Q_FOREACH(const QColor &layerColor, layerColors) { Q_FOREACH(const QColor &overlayColor, overlayColors) { Q_FOREACH(const QColor &bgColor, bgColors) { bg->setPixel(0, y, layerColor); bg->setPixel(1, y, overlayColor); bg->setPixel(2, y, bgColor); bg->setPixel(3, y, QColor(layerOpacity, layerOpacity, layerOpacity, 255)); bg->setPixel(4, y, QColor(overlayOpacity, overlayOpacity, overlayOpacity, 255)); for (int i = 5; i < width; i++) { bg->setPixel(i, y, bgColor); } for (int i = 0; i <= 10; i++) { const quint8 alpha = i == 0 ? 71 : qRound(255 * qreal(i) / 10); { QColor c(layerColor); c.setAlpha(alpha); layer->setPixel(7 + i, y, c); } { QColor c(overlayColor); c.setAlpha(alpha); overlay->setPixel(7 + i, y, c); } } y++; } } } const QRect rc = bg->exactBounds() | layer->exactBounds(); KIS_DUMP_DEVICE_2(layer, rc, "00_layer", "dd"); KIS_DUMP_DEVICE_2(overlay, rc, "01_overlay", "dd"); KIS_DUMP_DEVICE_2(bg, rc, "02_bg", "dd"); KisPaintDeviceSP originalBg = new KisPaintDevice(*bg); KisSelectionSP selection = new KisSelection(); KisLsUtils::selectionFromAlphaChannel(layer, selection, rc); { KisSequentialIterator it(layer, rc); while (it.nextPixel()) { cs->setOpacity(it.rawData(), quint8(255), 1); } } { KisSequentialIterator it(overlay, rc); while (it.nextPixel()) { cs->setOpacity(it.rawData(), quint8(255), 1); } } KisPainter painter(bg); painter.setOpacity(layerOpacity); painter.setCompositeOp(COMPOSITE_OVER); painter.bitBlt(rc.topLeft(), layer, rc); painter.setOpacity(overlayOpacity); painter.setCompositeOp(COMPOSITE_ADD); painter.bitBlt(rc.topLeft(), overlay, rc); KIS_DUMP_DEVICE_2(bg, rc, "03_result", "dd"); KisPainter bgPainter(originalBg); bgPainter.setCompositeOp(COMPOSITE_COPY); bgPainter.setSelection(selection); bgPainter.bitBlt(rc.topLeft(), bg, rc); KIS_DUMP_DEVICE_2(originalBg, rc, "04_knockout", "dd"); } -QTEST_MAIN(KisLayerStyleProjectionPlaneTest) +KISTEST_MAIN(KisLayerStyleProjectionPlaneTest) diff --git a/libs/libkis/FillLayer.cpp b/libs/libkis/FillLayer.cpp index 1184182771..3640bb5cf8 100644 --- a/libs/libkis/FillLayer.cpp +++ b/libs/libkis/FillLayer.cpp @@ -1,75 +1,78 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "FillLayer.h" #include #include #include #include #include #include #include +#include FillLayer::FillLayer(KisImageSP image, QString name, KisFilterConfigurationSP filterConfig, Selection &selection, QObject *parent) : Node(image, new KisGeneratorLayer(image, name, filterConfig->cloneWithResourcesSnapshot(), selection.selection()), parent) { } FillLayer::FillLayer(KisGeneratorLayerSP layer, QObject *parent): Node(layer->image(), layer, parent) { } FillLayer::~FillLayer() { } QString FillLayer::generatorName() { const KisGeneratorLayer *layer = qobject_cast(this->node()); return layer->filter()->name(); } InfoObject * FillLayer::filterConfig() { const KisGeneratorLayer *layer = qobject_cast(this->node()); return new InfoObject(layer->filter()); } QString FillLayer::type() const { return "filllayer"; } bool FillLayer::setGenerator(const QString &generatorName, InfoObject *config) { KisGeneratorLayer *layer = dynamic_cast(this->node().data()); + KIS_ASSERT_RECOVER_RETURN_VALUE(layer, false); + //getting the default configuration here avoids trouble with versioning. KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorName); if (generator) { KisFilterConfigurationSP cfg = generator->factoryConfiguration(KisGlobalResourcesInterface::instance()); Q_FOREACH(const QString property, config->properties().keys()) { cfg->setProperty(property, config->property(property)); } layer->setFilter(cfg->cloneWithResourcesSnapshot()); return true; } return false; } diff --git a/libs/libkis/FilterMask.cpp b/libs/libkis/FilterMask.cpp index b027073194..366e8f1153 100644 --- a/libs/libkis/FilterMask.cpp +++ b/libs/libkis/FilterMask.cpp @@ -1,63 +1,69 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "FilterMask.h" #include #include #include #include #include FilterMask::FilterMask(KisImageSP image, QString name, Filter &filter, QObject *parent) : Node(image, new KisFilterMask(), parent) { this->node()->setName(name); KisFilterMask *mask = dynamic_cast(this->node().data()); + KIS_SAFE_ASSERT_RECOVER_RETURN(mask); + mask->setFilter(filter.filterConfig()->cloneWithResourcesSnapshot()); } FilterMask::FilterMask(KisImageSP image, KisFilterMaskSP mask, QObject *parent): Node(image, mask, parent) { } FilterMask::~FilterMask() { } QString FilterMask::type() const { return "filtermask"; } void FilterMask::setFilter(Filter &filter) { KisFilterMask *mask = dynamic_cast(this->node().data()); + KIS_SAFE_ASSERT_RECOVER_RETURN(mask); + mask->setFilter(filter.filterConfig()->cloneWithResourcesSnapshot()); } Filter * FilterMask::filter() { Filter* filter = new Filter(); const KisFilterMask *mask = qobject_cast(this->node()); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(mask, 0); + filter->setName(mask->filter()->name()); filter->setConfiguration(new InfoObject(mask->filter())); return filter; } diff --git a/libs/pigment/resources/KoSegmentGradient.cpp b/libs/pigment/resources/KoSegmentGradient.cpp index ce41c415dd..a590994efc 100644 --- a/libs/pigment/resources/KoSegmentGradient.cpp +++ b/libs/pigment/resources/KoSegmentGradient.cpp @@ -1,1080 +1,1080 @@ /* Copyright (c) 2000 Matthias Elter 2001 John Califf 2004 Boudewijn Rempt 2004 Adrian Page 2004, 2007 Sven Langkamp This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColorSpace.h" #include "KoMixColorsOp.h" #include #include #include KoGradientSegment::RGBColorInterpolationStrategy *KoGradientSegment::RGBColorInterpolationStrategy::m_instance = 0; KoGradientSegment::HSVCWColorInterpolationStrategy *KoGradientSegment::HSVCWColorInterpolationStrategy::m_instance = 0; KoGradientSegment::HSVCCWColorInterpolationStrategy *KoGradientSegment::HSVCCWColorInterpolationStrategy::m_instance = 0; KoGradientSegment::LinearInterpolationStrategy *KoGradientSegment::LinearInterpolationStrategy::m_instance = 0; KoGradientSegment::CurvedInterpolationStrategy *KoGradientSegment::CurvedInterpolationStrategy::m_instance = 0; KoGradientSegment::SineInterpolationStrategy *KoGradientSegment::SineInterpolationStrategy::m_instance = 0; KoGradientSegment::SphereIncreasingInterpolationStrategy *KoGradientSegment::SphereIncreasingInterpolationStrategy::m_instance = 0; KoGradientSegment::SphereDecreasingInterpolationStrategy *KoGradientSegment::SphereDecreasingInterpolationStrategy::m_instance = 0; KoSegmentGradient::KoSegmentGradient(const QString& file) : KoAbstractGradient(file) { } KoSegmentGradient::~KoSegmentGradient() { for (int i = 0; i < m_segments.count(); i++) { delete m_segments[i]; m_segments[i] = 0; } } KoSegmentGradient::KoSegmentGradient(const KoSegmentGradient &rhs) : KoAbstractGradient(rhs) { Q_FOREACH (KoGradientSegment *segment, rhs.m_segments) { pushSegment(new KoGradientSegment(*segment)); } } KoResourceSP KoSegmentGradient::clone() const { return KoResourceSP(new KoSegmentGradient(*this)); } bool KoSegmentGradient::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) { Q_UNUSED(resourcesInterface); QByteArray data = dev->readAll(); QTextStream fileContent(data, QIODevice::ReadOnly); fileContent.setAutoDetectUnicode(true); QString header = fileContent.readLine(); if (header != "GIMP Gradient") { return false; } QString nameDefinition = fileContent.readLine(); QString numSegmentsText; if (nameDefinition.startsWith("Name: ")) { QString nameText = nameDefinition.right(nameDefinition.length() - 6); setName(nameText); numSegmentsText = fileContent.readLine(); } else { // Older format without name. numSegmentsText = nameDefinition; } dbgPigment << "Loading gradient: " << name(); int numSegments; bool ok; numSegments = numSegmentsText.toInt(&ok); if (!ok || numSegments < 1) { return false; } dbgPigment << "Number of segments = " << numSegments; const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); for (int i = 0; i < numSegments; i++) { QString segmentText = fileContent.readLine(); QTextStream segmentFields(&segmentText); QStringList values = segmentText.split(' '); qreal leftOffset = values[0].toDouble(); qreal middleOffset = values[1].toDouble(); qreal rightOffset = values[2].toDouble(); qreal leftRed = values[3].toDouble(); qreal leftGreen = values[4].toDouble(); qreal leftBlue = values[5].toDouble(); qreal leftAlpha = values[6].toDouble(); qreal rightRed = values[7].toDouble(); qreal rightGreen = values[8].toDouble(); qreal rightBlue = values[9].toDouble(); qreal rightAlpha = values[10].toDouble(); int interpolationType = values[11].toInt(); int colorInterpolationType = values[12].toInt(); KoGradientSegmentEndpointType startType, endType; if (values.count() >= 15) { //file supports FG/BG colors startType = static_cast(values[13].toInt()); endType = static_cast(values[14].toInt()); } else { startType = endType = COLOR_ENDPOINT; } quint8 data[4]; data[2] = static_cast(leftRed * 255 + 0.5); data[1] = static_cast(leftGreen * 255 + 0.5); data[0] = static_cast(leftBlue * 255 + 0.5); data[3] = static_cast(leftAlpha * OPACITY_OPAQUE_U8 + 0.5); KoColor leftColor(data, rgbColorSpace); data[2] = static_cast(rightRed * 255 + 0.5); data[1] = static_cast(rightGreen * 255 + 0.5); data[0] = static_cast(rightBlue * 255 + 0.5); data[3] = static_cast(rightAlpha * OPACITY_OPAQUE_U8 + 0.5); KoColor rightColor(data, rgbColorSpace); KoGradientSegmentEndpoint left(leftOffset, leftColor, startType); KoGradientSegmentEndpoint right(rightOffset, rightColor, endType); KoGradientSegment *segment = new KoGradientSegment(interpolationType, colorInterpolationType, left, right, middleOffset); Q_CHECK_PTR(segment); if (!segment -> isValid()) { delete segment; return false; } m_segments.push_back(segment); } if (!m_segments.isEmpty()) { updatePreview(); setValid(true); return true; } else { return false; } } bool KoSegmentGradient::saveToDevice(QIODevice *dev) const { QTextStream fileContent(dev); fileContent << "GIMP Gradient\n"; fileContent << "Name: " << name() << "\n"; fileContent << m_segments.count() << "\n"; Q_FOREACH (KoGradientSegment* segment, m_segments) { fileContent << QString::number(segment->startOffset(), 'f') << " " << QString::number(segment->middleOffset(), 'f') << " " << QString::number(segment->endOffset(), 'f') << " "; QColor startColor = segment->startColor().toQColor(); QColor endColor = segment->endColor().toQColor(); fileContent << QString::number(startColor.redF(), 'f') << " " << QString::number(startColor.greenF(), 'f') << " " << QString::number(startColor.blueF(), 'f') << " " << QString::number(startColor.alphaF(), 'f') << " "; fileContent << QString::number(endColor.redF(), 'f') << " " << QString::number(endColor.greenF(), 'f') << " " << QString::number(endColor.blueF(), 'f') << " " << QString::number(endColor.alphaF(), 'f') << " "; fileContent << (int)segment->interpolation() << " " << (int)segment->colorInterpolation() << " "; fileContent << (int)segment->startType() << " " << (int)segment->endType() << "\n"; } KoResource::saveToDevice(dev); return true; } KoGradientSegment *KoSegmentGradient::segmentAt(qreal t) const { if (t < 0.0) return 0; if (t > 1.0) return 0; if (m_segments.isEmpty()) return 0; for (QList::const_iterator it = m_segments.begin(); it != m_segments.end(); ++it) { if (t > (*it)->startOffset() - DBL_EPSILON && t < (*it)->endOffset() + DBL_EPSILON) { return *it; } } return 0; } void KoSegmentGradient::colorAt(KoColor& dst, qreal t) const { const KoGradientSegment *segment = segmentAt(t); if (segment) { segment->colorAt(dst, t); } } QGradient* KoSegmentGradient::toQGradient() const { QGradient* gradient = new QLinearGradient(); QColor color; Q_FOREACH (KoGradientSegment* segment, m_segments) { segment->startColor().toQColor(&color); gradient->setColorAt(segment->startOffset() , color); segment->endColor().toQColor(&color); gradient->setColorAt(segment->endOffset() , color); } return gradient; } QString KoSegmentGradient::defaultFileExtension() const { return QString(".ggr"); } void KoSegmentGradient::toXML(QDomDocument &doc, QDomElement &gradientElt) const { gradientElt.setAttribute("type", "segment"); Q_FOREACH(KoGradientSegment *segment, this->segments()) { QDomElement segmentElt = doc.createElement("segment"); QDomElement start = doc.createElement("start"); QDomElement end = doc.createElement("end"); segmentElt.setAttribute("start-offset", KisDomUtils::toString(segment->startOffset())); const KoColor startColor = segment->startColor(); segmentElt.setAttribute("start-bitdepth", startColor.colorSpace()->colorDepthId().id()); segmentElt.setAttribute("start-alpha", KisDomUtils::toString(startColor.opacityF())); segmentElt.setAttribute("start-type", KisDomUtils::toString(segment->startType())); startColor.toXML(doc, start); segmentElt.setAttribute("middle-offset", KisDomUtils::toString(segment->middleOffset())); segmentElt.setAttribute("end-offset", KisDomUtils::toString(segment->endOffset())); const KoColor endColor = segment->endColor(); segmentElt.setAttribute("end-bitdepth", endColor.colorSpace()->colorDepthId().id()); segmentElt.setAttribute("end-alpha", KisDomUtils::toString(endColor.opacityF())); segmentElt.setAttribute("end-type", KisDomUtils::toString(segment->endType())); endColor.toXML(doc, end); segmentElt.setAttribute("interpolation", KisDomUtils::toString(segment->interpolation())); segmentElt.setAttribute("color-interpolation", KisDomUtils::toString(segment->colorInterpolation())); segmentElt.appendChild(start); segmentElt.appendChild(end); gradientElt.appendChild(segmentElt); } } KoSegmentGradient KoSegmentGradient::fromXML(const QDomElement &elt) { KoSegmentGradient gradient; QDomElement segmentElt = elt.firstChildElement("segment"); while (!segmentElt.isNull()) { int interpolation = KisDomUtils::toInt(segmentElt.attribute("interpolation", "0.0")); int colorInterpolation = KisDomUtils::toInt(segmentElt.attribute("color-interpolation", "0.0")); double startOffset = KisDomUtils::toDouble(segmentElt.attribute("start-offset", "0.0")); qreal middleOffset = KisDomUtils::toDouble(segmentElt.attribute("middle-offset", "0.0")); qreal endOffset = KisDomUtils::toDouble(segmentElt.attribute("end-offset", "0.0")); QDomElement start = segmentElt.firstChildElement("start"); QString startBitdepth = segmentElt.attribute("start-bitdepth", Integer8BitsColorDepthID.id()); QColor left = KoColor::fromXML(start.firstChildElement(), startBitdepth).toQColor(); left.setAlphaF(KisDomUtils::toDouble(segmentElt.attribute("start-alpha", "1.0"))); QString endBitdepth = segmentElt.attribute("end-bitdepth", Integer8BitsColorDepthID.id()); QDomElement end = segmentElt.firstChildElement("end"); QColor right = KoColor::fromXML(end.firstChildElement(), endBitdepth).toQColor(); right.setAlphaF(KisDomUtils::toDouble(segmentElt.attribute("end-alpha", "1.0"))); KoGradientSegmentEndpointType leftType = static_cast(KisDomUtils::toInt(segmentElt.attribute("start-type", "0"))); KoGradientSegmentEndpointType rightType = static_cast(KisDomUtils::toInt(segmentElt.attribute("end-type", "0"))); gradient.createSegment(interpolation, colorInterpolation, startOffset, endOffset, middleOffset, left, right, leftType, rightType); segmentElt = segmentElt.nextSiblingElement("segment"); } return gradient; } KoGradientSegment::KoGradientSegment(int interpolationType, int colorInterpolationType, KoGradientSegmentEndpoint start, KoGradientSegmentEndpoint end, qreal middleOffset) : m_start(start), m_end(end) { m_interpolator = 0; switch (interpolationType) { case INTERP_LINEAR: m_interpolator = LinearInterpolationStrategy::instance(); break; case INTERP_CURVED: m_interpolator = CurvedInterpolationStrategy::instance(); break; case INTERP_SINE: m_interpolator = SineInterpolationStrategy::instance(); break; case INTERP_SPHERE_INCREASING: m_interpolator = SphereIncreasingInterpolationStrategy::instance(); break; case INTERP_SPHERE_DECREASING: m_interpolator = SphereDecreasingInterpolationStrategy::instance(); break; } m_colorInterpolator = 0; switch (colorInterpolationType) { case COLOR_INTERP_RGB: m_colorInterpolator = RGBColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CCW: m_colorInterpolator = HSVCCWColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CW: m_colorInterpolator = HSVCWColorInterpolationStrategy::instance(); break; } if (m_start.offset < DBL_EPSILON) { m_start.offset = 0; } else if (m_start.offset > 1 - DBL_EPSILON) { m_start.offset = 1; } if (middleOffset < m_start.offset + DBL_EPSILON) { m_middleOffset = m_start.offset; } else if (middleOffset > 1 - DBL_EPSILON) { m_middleOffset = 1; } else { m_middleOffset = middleOffset; } if (m_end.offset < m_middleOffset + DBL_EPSILON) { m_end.offset = m_middleOffset; } else if (m_end.offset > 1 - DBL_EPSILON) { m_end.offset = 1; } m_length = m_end.offset - m_start.offset; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_start.offset) / m_length; } m_hasVariableColors = m_start.type != COLOR_ENDPOINT || m_end.type != COLOR_ENDPOINT; } const KoColor& KoGradientSegment::startColor() const { return m_start.color; } const KoColor& KoGradientSegment::endColor() const { return m_end.color; } qreal KoGradientSegment::startOffset() const { return m_start.offset; } qreal KoGradientSegment::middleOffset() const { return m_middleOffset; } qreal KoGradientSegment::endOffset() const { return m_end.offset; } -const KoGradientSegmentEndpointType KoGradientSegment::startType() const +KoGradientSegmentEndpointType KoGradientSegment::startType() const { return m_start.type; } -const KoGradientSegmentEndpointType KoGradientSegment::endType() const +KoGradientSegmentEndpointType KoGradientSegment::endType() const { return m_end.type; } void KoGradientSegment::setStartType(KoGradientSegmentEndpointType type) { m_start.type = type; if (type != COLOR_ENDPOINT) { m_hasVariableColors = true; } else if (m_end.type == COLOR_ENDPOINT) { m_hasVariableColors = false; } } void KoGradientSegment::setEndType(KoGradientSegmentEndpointType type) { m_end.type = type; if (type != COLOR_ENDPOINT) { m_hasVariableColors = true; } else if (m_start.type == COLOR_ENDPOINT) { m_hasVariableColors = false; } } void KoGradientSegment::setStartOffset(qreal t) { m_start.offset = t; m_length = m_end.offset - m_start.offset; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_start.offset) / m_length; } } void KoGradientSegment::setMiddleOffset(qreal t) { m_middleOffset = t; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_start.offset) / m_length; } } void KoGradientSegment::setEndOffset(qreal t) { m_end.offset = t; m_length = m_end.offset - m_start.offset; if (m_length < DBL_EPSILON) { m_middleT = 0.5; } else { m_middleT = (m_middleOffset - m_start.offset) / m_length; } } void KoGradientSegment::setVariableColors(const KoColor& foreground, const KoColor& background) { switch (m_start.type) { case COLOR_ENDPOINT: break; case FOREGROUND_ENDPOINT: m_start.color = foreground; break; case FOREGROUND_TRANSPARENT_ENDPOINT: //TODO: add Transparent options to gradient editor... m_start.color = foreground; m_start.color.setOpacity(quint8(0)); break; case BACKGROUND_ENDPOINT: m_start.color = background; break; case BACKGROUND_TRANSPARENT_ENDPOINT: m_start.color = background; m_start.color.setOpacity(quint8(0)); break; } switch (m_end.type) { case COLOR_ENDPOINT: break; case FOREGROUND_ENDPOINT: m_end.color = foreground; break; case FOREGROUND_TRANSPARENT_ENDPOINT: m_end.color = foreground; m_end.color.setOpacity(quint8(0)); break; case BACKGROUND_ENDPOINT: m_end.color = background; break; case BACKGROUND_TRANSPARENT_ENDPOINT: m_end.color = background; m_end.color.setOpacity(quint8(0)); break; } } bool KoGradientSegment::hasVariableColors() { return m_hasVariableColors; } int KoGradientSegment::interpolation() const { return m_interpolator->type(); } void KoGradientSegment::setInterpolation(int interpolationType) { switch (interpolationType) { case INTERP_LINEAR: m_interpolator = LinearInterpolationStrategy::instance(); break; case INTERP_CURVED: m_interpolator = CurvedInterpolationStrategy::instance(); break; case INTERP_SINE: m_interpolator = SineInterpolationStrategy::instance(); break; case INTERP_SPHERE_INCREASING: m_interpolator = SphereIncreasingInterpolationStrategy::instance(); break; case INTERP_SPHERE_DECREASING: m_interpolator = SphereDecreasingInterpolationStrategy::instance(); break; } } int KoGradientSegment::colorInterpolation() const { return m_colorInterpolator->type(); } void KoGradientSegment::setColorInterpolation(int colorInterpolationType) { switch (colorInterpolationType) { case COLOR_INTERP_RGB: m_colorInterpolator = RGBColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CCW: m_colorInterpolator = HSVCCWColorInterpolationStrategy::instance(); break; case COLOR_INTERP_HSV_CW: m_colorInterpolator = HSVCWColorInterpolationStrategy::instance(); break; } } void KoGradientSegment::colorAt(KoColor& dst, qreal t) const { Q_ASSERT(t > m_start.offset - DBL_EPSILON && t < m_end.offset + DBL_EPSILON); qreal segmentT; if (m_length < DBL_EPSILON) { segmentT = 0.5; } else { segmentT = (t - m_start.offset) / m_length; } qreal colorT = m_interpolator->valueAt(segmentT, m_middleT); m_colorInterpolator->colorAt(dst, colorT, m_start.color, m_end.color); } void KoGradientSegment::mirrorSegment() { KoColor tmpColor = startColor(); setStartColor(endColor()); setEndColor(tmpColor); KoGradientSegmentEndpointType tmpType = startType(); setStartType(endType()); setEndType(tmpType); setMiddleOffset(endOffset() - (middleOffset() - startOffset())); if (interpolation() == INTERP_SPHERE_INCREASING) { setInterpolation(INTERP_SPHERE_DECREASING); } else if (interpolation() == INTERP_SPHERE_DECREASING) { setInterpolation(INTERP_SPHERE_INCREASING); } if (colorInterpolation() == COLOR_INTERP_HSV_CW) { setColorInterpolation(COLOR_INTERP_HSV_CCW); } else if (colorInterpolation() == COLOR_INTERP_HSV_CCW) { setColorInterpolation(COLOR_INTERP_HSV_CW); } } bool KoGradientSegment::isValid() const { if (m_interpolator == 0 || m_colorInterpolator == 0) return false; return true; } KoGradientSegment::RGBColorInterpolationStrategy::RGBColorInterpolationStrategy() : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8()) { } KoGradientSegment::RGBColorInterpolationStrategy *KoGradientSegment::RGBColorInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new RGBColorInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } void KoGradientSegment::RGBColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& _start, const KoColor& _end) const { KoColor buffer(m_colorSpace); KoColor start(m_colorSpace); KoColor end(m_colorSpace); KoColor startDummy, endDummy; //hack to get a color space with the bitdepth of the gradients(8bit), but with the colour profile of the image// const KoColorSpace* mixSpace = KoColorSpaceRegistry::instance()->rgb8(dst.colorSpace()->profile()); //convert to the right colorspace for the start and end if we have our mixSpace. if (mixSpace){ startDummy = KoColor(_start, mixSpace); endDummy = KoColor(_end, mixSpace); } else { startDummy = _start; endDummy = _end; } start.fromKoColor(_start); end.fromKoColor(_end); const quint8 *colors[2]; colors[0] = startDummy.data(); colors[1] = endDummy.data(); qint16 colorWeights[2]; colorWeights[0] = static_cast((1.0 - t) * 255 + 0.5); colorWeights[1] = 255 - colorWeights[0]; //check if our mixspace exists, it doesn't at startup. if (mixSpace){ if (*buffer.colorSpace() != *mixSpace) { buffer = KoColor(mixSpace); } mixSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data()); } else { buffer = KoColor(m_colorSpace); m_colorSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data()); } dst.fromKoColor(buffer); } KoGradientSegment::HSVCWColorInterpolationStrategy::HSVCWColorInterpolationStrategy() : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8()) { } KoGradientSegment::HSVCWColorInterpolationStrategy *KoGradientSegment::HSVCWColorInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new HSVCWColorInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } void KoGradientSegment::HSVCWColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const { QColor sc; QColor ec; start.toQColor(&sc); end.toQColor(&ec); int s = static_cast(sc.saturation() + t * (ec.saturation() - sc.saturation()) + 0.5); int v = static_cast(sc.value() + t * (ec.value() - sc.value()) + 0.5); int h; if (ec.hue() < sc.hue()) { h = static_cast(ec.hue() + (1 - t) * (sc.hue() - ec.hue()) + 0.5); } else { h = static_cast(ec.hue() + (1 - t) * (360 - ec.hue() + sc.hue()) + 0.5); if (h > 359) { h -= 360; } } // XXX: added an explicit cast. Is this correct? quint8 opacity = static_cast(sc.alpha() + t * (ec.alpha() - sc.alpha())); QColor result; result.setHsv(h, s, v); result.setAlpha(opacity); dst.fromQColor(result); } KoGradientSegment::HSVCCWColorInterpolationStrategy::HSVCCWColorInterpolationStrategy() : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8()) { } KoGradientSegment::HSVCCWColorInterpolationStrategy *KoGradientSegment::HSVCCWColorInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new HSVCCWColorInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } void KoGradientSegment::HSVCCWColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const { QColor sc; QColor se; start.toQColor(&sc); end.toQColor(&se); int s = static_cast(sc.saturation() + t * (se.saturation() - sc.saturation()) + 0.5); int v = static_cast(sc.value() + t * (se.value() - sc.value()) + 0.5); int h; if (sc.hue() < se.hue()) { h = static_cast(sc.hue() + t * (se.hue() - sc.hue()) + 0.5); } else { h = static_cast(sc.hue() + t * (360 - sc.hue() + se.hue()) + 0.5); if (h > 359) { h -= 360; } } // XXX: Added an explicit static cast quint8 opacity = static_cast(sc.alpha() + t * (se.alpha() - sc.alpha())); QColor result; result.setHsv(h, s, v); result.setAlpha(opacity); dst.fromQColor(result); } KoGradientSegment::LinearInterpolationStrategy *KoGradientSegment::LinearInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new LinearInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::LinearInterpolationStrategy::calcValueAt(qreal t, qreal middle) { Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON); Q_ASSERT(middle > -DBL_EPSILON && middle < 1 + DBL_EPSILON); qreal value = 0; if (t <= middle) { if (middle < DBL_EPSILON) { value = 0; } else { value = (t / middle) * 0.5; } } else { if (middle > 1 - DBL_EPSILON) { value = 1; } else { value = ((t - middle) / (1 - middle)) * 0.5 + 0.5; } } return value; } qreal KoGradientSegment::LinearInterpolationStrategy::valueAt(qreal t, qreal middle) const { return calcValueAt(t, middle); } KoGradientSegment::CurvedInterpolationStrategy::CurvedInterpolationStrategy() { m_logHalf = log(0.5); } KoGradientSegment::CurvedInterpolationStrategy *KoGradientSegment::CurvedInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new CurvedInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::CurvedInterpolationStrategy::valueAt(qreal t, qreal middle) const { Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON); Q_ASSERT(middle > -DBL_EPSILON && middle < 1 + DBL_EPSILON); qreal value = 0; if (middle < DBL_EPSILON) { middle = DBL_EPSILON; } value = pow(t, m_logHalf / log(middle)); return value; } KoGradientSegment::SineInterpolationStrategy *KoGradientSegment::SineInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new SineInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::SineInterpolationStrategy::valueAt(qreal t, qreal middle) const { qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle); qreal value = (sin(-M_PI_2 + M_PI * lt) + 1.0) / 2.0; return value; } KoGradientSegment::SphereIncreasingInterpolationStrategy *KoGradientSegment::SphereIncreasingInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new SphereIncreasingInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::SphereIncreasingInterpolationStrategy::valueAt(qreal t, qreal middle) const { qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle) - 1; qreal value = sqrt(1 - lt * lt); return value; } KoGradientSegment::SphereDecreasingInterpolationStrategy *KoGradientSegment::SphereDecreasingInterpolationStrategy::instance() { if (m_instance == 0) { m_instance = new SphereDecreasingInterpolationStrategy(); Q_CHECK_PTR(m_instance); } return m_instance; } qreal KoGradientSegment::SphereDecreasingInterpolationStrategy::valueAt(qreal t, qreal middle) const { qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle); qreal value = 1 - sqrt(1 - lt * lt); return value; } void KoSegmentGradient::createSegment(int interpolation, int colorInterpolation, double startOffset, double endOffset, double middleOffset, const QColor & leftColor, const QColor & rightColor, KoGradientSegmentEndpointType leftType, KoGradientSegmentEndpointType rightType) { KoGradientSegmentEndpoint left(startOffset, KoColor(leftColor, colorSpace()), leftType); KoGradientSegmentEndpoint right(endOffset, KoColor(rightColor, colorSpace()), rightType); pushSegment(new KoGradientSegment(interpolation, colorInterpolation, left, right, middleOffset)); } const QList KoSegmentGradient::getHandlePositions() const { QList handlePositions; handlePositions.push_back(m_segments[0]->startOffset()); for (int i = 0; i < m_segments.count(); i++) { handlePositions.push_back(m_segments[i]->endOffset()); } return handlePositions; } const QList KoSegmentGradient::getMiddleHandlePositions() const { QList middleHandlePositions; for (int i = 0; i < m_segments.count(); i++) { middleHandlePositions.push_back(m_segments[i]->middleOffset()); } return middleHandlePositions; } void KoSegmentGradient::moveSegmentStartOffset(KoGradientSegment* segment, double t) { QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { if (it == m_segments.begin()) { segment->setStartOffset(0.0); return; } KoGradientSegment* previousSegment = (*(it - 1)); if (t > segment->startOffset()) { if (t > segment->middleOffset()) t = segment->middleOffset(); } else { if (t < previousSegment->middleOffset()) t = previousSegment->middleOffset(); } previousSegment->setEndOffset(t); segment->setStartOffset(t); } } void KoSegmentGradient::moveSegmentEndOffset(KoGradientSegment* segment, double t) { QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { if (it + 1 == m_segments.end()) { segment->setEndOffset(1.0); return; } KoGradientSegment* followingSegment = (*(it + 1)); if (t < segment->endOffset()) { if (t < segment->middleOffset()) t = segment->middleOffset(); } else { if (t > followingSegment->middleOffset()) t = followingSegment->middleOffset(); } followingSegment->setStartOffset(t); segment->setEndOffset(t); } } void KoSegmentGradient::moveSegmentMiddleOffset(KoGradientSegment* segment, double t) { if (segment) { if (t > segment->endOffset()) segment->setMiddleOffset(segment->endOffset()); else if (t < segment->startOffset()) segment->setMiddleOffset(segment->startOffset()); else segment->setMiddleOffset(t); } } void KoSegmentGradient::splitSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { KoColor midleoffsetColor(segment->endColor().colorSpace()); segment->colorAt(midleoffsetColor, segment->middleOffset()); KoGradientSegmentEndpoint left(segment->startOffset(), segment->startColor(), segment->startType()); KoGradientSegmentEndpoint right(segment->middleOffset(), midleoffsetColor, COLOR_ENDPOINT); KoGradientSegment* newSegment = new KoGradientSegment( segment->interpolation(), segment->colorInterpolation(), left, right, (segment->middleOffset() - segment->startOffset()) / 2 + segment->startOffset()); m_segments.insert(it, newSegment); segment->setStartColor(midleoffsetColor); segment->setStartOffset(segment->middleOffset()); segment->setMiddleOffset((segment->endOffset() - segment->startOffset()) / 2 + segment->startOffset()); } } void KoSegmentGradient::duplicateSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { double middlePostionPercentage = (segment->middleOffset() - segment->startOffset()) / segment->length(); double center = segment->startOffset() + segment->length() / 2; KoGradientSegmentEndpoint left(segment->startOffset(), segment->startColor(), segment->startType()); KoGradientSegmentEndpoint right(center, segment->endColor(), segment->endType()); KoGradientSegment* newSegment = new KoGradientSegment( segment->interpolation(), segment->colorInterpolation(), left, right, segment->length() / 2 * middlePostionPercentage + segment->startOffset()); m_segments.insert(it, newSegment); segment->setStartOffset(center); segment->setMiddleOffset(segment->length() * middlePostionPercentage + segment->startOffset()); } } void KoSegmentGradient::mirrorSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); segment->mirrorSegment(); } KoGradientSegment* KoSegmentGradient::removeSegment(KoGradientSegment* segment) { Q_ASSERT(segment != 0); if (m_segments.count() < 2) return 0; QList::iterator it = std::find(m_segments.begin(), m_segments.end(), segment); if (it != m_segments.end()) { double middlePostionPercentage; KoGradientSegment* nextSegment; if (it == m_segments.begin()) { nextSegment = (*(it + 1)); middlePostionPercentage = (nextSegment->middleOffset() - nextSegment->startOffset()) / nextSegment->length(); nextSegment->setStartOffset(segment->startOffset()); nextSegment->setMiddleOffset(middlePostionPercentage * nextSegment->length() + nextSegment->startOffset()); } else { nextSegment = (*(it - 1)); middlePostionPercentage = (nextSegment->middleOffset() - nextSegment->startOffset()) / nextSegment->length(); nextSegment->setEndOffset(segment->endOffset()); nextSegment->setMiddleOffset(middlePostionPercentage * nextSegment->length() + nextSegment->startOffset()); } delete segment; m_segments.erase(it); return nextSegment; } return 0; } bool KoSegmentGradient::removeSegmentPossible() const { if (m_segments.count() < 2) return false; return true; } const QList& KoSegmentGradient::segments() const { return m_segments; } bool KoSegmentGradient::hasVariableColors() const { for (int i = 0; i < m_segments.count(); i++) { if (m_segments[i]->hasVariableColors()) { return true; } } return false; } void KoSegmentGradient::setVariableColors(const KoColor& foreground, const KoColor& background) { for (int i = 0; i < m_segments.count(); i++) { m_segments[i]->setVariableColors(foreground, background); } } diff --git a/libs/pigment/resources/KoSegmentGradient.h b/libs/pigment/resources/KoSegmentGradient.h index 24cfd50aad..99cb4c0472 100644 --- a/libs/pigment/resources/KoSegmentGradient.h +++ b/libs/pigment/resources/KoSegmentGradient.h @@ -1,484 +1,484 @@ /* Copyright (c) 2000 Matthias Elter 2004 Boudewijn Rempt 2004 Adrian Page 2004, 2007 Sven Langkamp 2017 Wolthera van Hövell tot Westerflier This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KOSEGMENTGRADIENT_H #define KOSEGMENTGRADIENT_H #include #include #include #include #include "KoColor.h" #include enum { INTERP_LINEAR = 0, INTERP_CURVED, INTERP_SINE, INTERP_SPHERE_INCREASING, INTERP_SPHERE_DECREASING }; enum { COLOR_INTERP_RGB, COLOR_INTERP_HSV_CCW, COLOR_INTERP_HSV_CW }; //For saving to .ggr to match GIMP format, we also have Foreground (transparent) and Background (transparent) modes, currently unused... enum KoGradientSegmentEndpointType { COLOR_ENDPOINT, FOREGROUND_ENDPOINT, FOREGROUND_TRANSPARENT_ENDPOINT, BACKGROUND_ENDPOINT, BACKGROUND_TRANSPARENT_ENDPOINT }; struct KoGradientSegmentEndpoint { KoGradientSegmentEndpoint(qreal _off, KoColor _color, KoGradientSegmentEndpointType _type) : offset(_off), color(_color), type(_type) { } qreal offset; KoColor color; KoGradientSegmentEndpointType type; }; /// Write API docs here class KRITAPIGMENT_EXPORT KoGradientSegment { public: KoGradientSegment(int interpolationType, int colorInterpolationType, KoGradientSegmentEndpoint start, KoGradientSegmentEndpoint end, qreal middleOffset); // startOffset <= t <= endOffset void colorAt(KoColor&, qreal t) const; const KoColor& startColor() const; const KoColor& endColor() const; - const KoGradientSegmentEndpointType startType() const; - const KoGradientSegmentEndpointType endType() const; + KoGradientSegmentEndpointType startType() const; + KoGradientSegmentEndpointType endType() const; void setStartColor(const KoColor& color) { m_start.color = color; if (m_start.type == FOREGROUND_TRANSPARENT_ENDPOINT || m_start.type == BACKGROUND_TRANSPARENT_ENDPOINT) { m_start.color.setOpacity(quint8(0)); } else if (m_start.type == FOREGROUND_ENDPOINT || m_start.type == BACKGROUND_ENDPOINT) { m_start.color.setOpacity(quint8(255)); } } void setEndColor(const KoColor& color) { m_end.color = color; if (m_end.type == FOREGROUND_TRANSPARENT_ENDPOINT || m_end.type == BACKGROUND_TRANSPARENT_ENDPOINT) { m_end.color.setOpacity(quint8(0)); } else if (m_end.type == FOREGROUND_ENDPOINT || m_end.type == BACKGROUND_ENDPOINT) { m_end.color.setOpacity(quint8(255)); } } void setStartType(KoGradientSegmentEndpointType type); void setEndType(KoGradientSegmentEndpointType type); qreal startOffset() const; qreal middleOffset() const; qreal endOffset() const; void setStartOffset(qreal t); void setMiddleOffset(qreal t); void setEndOffset(qreal t); void setVariableColors(const KoColor& foreground, const KoColor& background); bool hasVariableColors(); qreal length() { return m_length; } int interpolation() const; int colorInterpolation() const; void setInterpolation(int interpolationType); void setColorInterpolation(int colorInterpolationType); void mirrorSegment(); bool isValid() const; protected: class ColorInterpolationStrategy { public: ColorInterpolationStrategy() {} virtual ~ColorInterpolationStrategy() {} virtual void colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const = 0; virtual int type() const = 0; }; class RGBColorInterpolationStrategy : public ColorInterpolationStrategy { public: static RGBColorInterpolationStrategy *instance(); void colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const override; int type() const override { return COLOR_INTERP_RGB; } private: RGBColorInterpolationStrategy(); static RGBColorInterpolationStrategy *m_instance; const KoColorSpace * const m_colorSpace; }; class HSVCWColorInterpolationStrategy : public ColorInterpolationStrategy { public: static HSVCWColorInterpolationStrategy *instance(); void colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const override; int type() const override { return COLOR_INTERP_HSV_CW; } private: HSVCWColorInterpolationStrategy(); static HSVCWColorInterpolationStrategy *m_instance; const KoColorSpace * const m_colorSpace; }; class HSVCCWColorInterpolationStrategy : public ColorInterpolationStrategy { public: static HSVCCWColorInterpolationStrategy *instance(); void colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const override; int type() const override { return COLOR_INTERP_HSV_CCW; } private: HSVCCWColorInterpolationStrategy(); static HSVCCWColorInterpolationStrategy *m_instance; const KoColorSpace * const m_colorSpace; }; class InterpolationStrategy { public: InterpolationStrategy() {} virtual ~InterpolationStrategy() {} virtual qreal valueAt(qreal t, qreal middle) const = 0; virtual int type() const = 0; }; class LinearInterpolationStrategy : public InterpolationStrategy { public: static LinearInterpolationStrategy *instance(); qreal valueAt(qreal t, qreal middle) const override; int type() const override { return INTERP_LINEAR; } // This does the actual calculation and is made // static as an optimization for the other // strategies that need this for their own calculation. static qreal calcValueAt(qreal t, qreal middle); private: LinearInterpolationStrategy() {} static LinearInterpolationStrategy *m_instance; }; class CurvedInterpolationStrategy : public InterpolationStrategy { public: static CurvedInterpolationStrategy *instance(); qreal valueAt(qreal t, qreal middle) const override; int type() const override { return INTERP_CURVED; } private: CurvedInterpolationStrategy(); static CurvedInterpolationStrategy *m_instance; qreal m_logHalf; }; class SphereIncreasingInterpolationStrategy : public InterpolationStrategy { public: static SphereIncreasingInterpolationStrategy *instance(); qreal valueAt(qreal t, qreal middle) const override; int type() const override { return INTERP_SPHERE_INCREASING; } private: SphereIncreasingInterpolationStrategy() {} static SphereIncreasingInterpolationStrategy *m_instance; }; class SphereDecreasingInterpolationStrategy : public InterpolationStrategy { public: static SphereDecreasingInterpolationStrategy *instance(); qreal valueAt(qreal t, qreal middle) const override; int type() const override { return INTERP_SPHERE_DECREASING; } private: SphereDecreasingInterpolationStrategy() {} static SphereDecreasingInterpolationStrategy *m_instance; }; class SineInterpolationStrategy : public InterpolationStrategy { public: static SineInterpolationStrategy *instance(); qreal valueAt(qreal t, qreal middle) const override; int type() const override { return INTERP_SINE; } private: SineInterpolationStrategy() {} static SineInterpolationStrategy *m_instance; }; private: InterpolationStrategy *m_interpolator; ColorInterpolationStrategy *m_colorInterpolator; qreal m_middleOffset; qreal m_length; qreal m_middleT; KoGradientSegmentEndpoint m_start, m_end; bool m_hasVariableColors = false; }; /** * KoSegmentGradient stores a segment based gradients like Gimp gradients */ class KRITAPIGMENT_EXPORT KoSegmentGradient : public KoAbstractGradient { public: explicit KoSegmentGradient(const QString &file = QString()); ~KoSegmentGradient() override; KoSegmentGradient(const KoSegmentGradient &rhs); KoSegmentGradient &operator=(const KoSegmentGradient &rhs) = delete; KoResourceSP clone() const override; bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override; bool saveToDevice(QIODevice* dev) const override; QPair resourceType() const override { return QPair(ResourceType::Gradients, ResourceSubType::SegmentedGradients); } /// reimplemented void colorAt(KoColor& dst, qreal t) const override; /// reimplemented bool hasVariableColors() const override; /// reimplemented void setVariableColors(const KoColor& foreground, const KoColor& background) override; /** * Returns the segment at a given position * @param t position inside the gradient, with 0 <= t <= 1 * @return the segment the position, 0 if no segment is found */ KoGradientSegment *segmentAt(qreal t) const; /// reimplemented QGradient* toQGradient() const override; /// reimplemented QString defaultFileExtension() const override; /** * @brief toXML * convert the gradient to xml. */ void toXML(QDomDocument& doc, QDomElement& gradientElt) const; /** * @brief fromXML * get a segment gradient from xml. * @return gradient */ static KoSegmentGradient fromXML(const QDomElement& elt); /** * a gradient colour picker can consist of one or more segments. * A segment has two end points - each colour in the gradient * colour picker represents a segment end point. * @param interpolation * @param colorInterpolation * @param startOffset * @param endOffset * @param middleOffset * @param left * @param right * @param leftType * @param rightType * @return void */ void createSegment(int interpolation, int colorInterpolation, double startOffset, double endOffset, double middleOffset, const QColor & leftColor, const QColor & rightColor, KoGradientSegmentEndpointType leftType = COLOR_ENDPOINT, KoGradientSegmentEndpointType rightType = COLOR_ENDPOINT); /** * gets a list of end points of the segments in the gradient * colour picker. If two colours, one segment then two end * points, and if three colours, then two segments with four * endpoints. * @return a list of double values */ const QList getHandlePositions() const; /** * gets a list of middle points of the segments in the gradient * colour picker. * @return a list of double values */ const QList getMiddleHandlePositions() const; /** * Moves the StartOffset of the specified segment to the * specified value and corrects the endoffset of the previous * segment. If the segment is the first Segment the startoffset * will be set to 0.0 . The offset will maximally be moved till * the middle of the current or the previous segment. This is * useful if someone clicks to move the handler for a segment, * to set the half the segment to the right and half the segment * to the left of the handler. * @param segment the segment for which to move the relative * offset within the gradient colour picker. * @param t the new startoff position for the segment * @return void */ void moveSegmentStartOffset(KoGradientSegment* segment, double t); /** * Moves the endoffset of the specified segment to the specified * value and corrects the startoffset of the following segment. * If the segment is the last segment the endoffset will be set * to 1.0 . The offset will maximally be moved till the middle * of the current or the following segment. This is useful if * someone moves the segment handler in the gradient colour * picker, and needs the segment to move with it. Sets the end * position of the segment to the correct new position. * @param segment the segment for which to move the relative * end position within the gradient colour picker. * @param t the new end position for the segment * @return void */ void moveSegmentEndOffset(KoGradientSegment* segment, double t); /** * moves the Middle of the specified segment to the specified * value. The offset will maximally be moved till the endoffset * or startoffset of the segment. This sets the middle of the * segment to the same position as the handler of the gradient * colour picker. * @param segment the segment for which to move the relative * middle position within the gradient colour picker. * @param t the new middle position for the segment * @return void */ void moveSegmentMiddleOffset(KoGradientSegment* segment, double t); /** * splits the specified segment into two equal parts * @param segment the segment to split * @return void */ void splitSegment(KoGradientSegment* segment); /** * duplicate the specified segment * @param segment the segment to duplicate * @return void */ void duplicateSegment(KoGradientSegment* segment); /** * create a segment horizontally reversed to the specified one. * @param segment the segment to reverse * @return void */ void mirrorSegment(KoGradientSegment* segment); /** * removes the specific segment from the gradient colour picker. * @param segment the segment to remove * @return the segment which will be at the place of the old * segment. 0 if the segment is not in the gradient or it is * not possible to remove the segment. */ KoGradientSegment* removeSegment(KoGradientSegment* segment); /** * checks if it's possible to remove a segment (at least two * segments in the gradient) * @return true if it's possible to remove an segment */ bool removeSegmentPossible() const; const QList& segments() const; protected: inline void pushSegment(KoGradientSegment* segment) { m_segments.push_back(segment); } QList m_segments; private: bool init(); }; typedef QSharedPointer KoSegmentGradientSP; #endif // KOSEGMENTGRADIENT_H diff --git a/libs/ui/kis_popup_palette.cpp b/libs/ui/kis_popup_palette.cpp index b0a68d6f70..10efb00c53 100644 --- a/libs/ui/kis_popup_palette.cpp +++ b/libs/ui/kis_popup_palette.cpp @@ -1,989 +1,990 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2011 Sven Langkamp Copyright 2016 Scott Petrovic This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_config.h" #include "kis_popup_palette.h" #include "kis_favorite_resource_manager.h" #include "kis_icon_utils.h" #include #include +#include "KoColorDisplayRendererInterface.h" #include #include #include "kis_signal_compressor.h" #include "brushhud/kis_brush_hud.h" #include "brushhud/kis_round_hud_button.h" #include "kis_signals_blocker.h" #include "kis_canvas_controller.h" #include "kis_acyclic_signal_connector.h" #include #include "KisMouseClickEater.h" class PopupColorTriangle : public KoTriangleColorSelector { public: PopupColorTriangle(const KoColorDisplayRendererInterface *displayRenderer, QWidget* parent) : KoTriangleColorSelector(displayRenderer, parent) , m_dragging(false) { } ~PopupColorTriangle() override {} void tabletEvent(QTabletEvent* event) override { event->accept(); QMouseEvent* mouseEvent = 0; // this will tell the pop-up palette widget to close if(event->button() == Qt::RightButton) { emit requestCloseContainer(); } // ignore any tablet events that are done with the right click // Tablet move events don't return a "button", so catch that too if(event->button() == Qt::LeftButton || event->type() == QEvent::TabletMove) { switch (event->type()) { case QEvent::TabletPress: mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); m_dragging = true; mousePressEvent(mouseEvent); break; case QEvent::TabletMove: mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(), (m_dragging) ? Qt::LeftButton : Qt::NoButton, (m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers()); mouseMoveEvent(mouseEvent); break; case QEvent::TabletRelease: mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); m_dragging = false; mouseReleaseEvent(mouseEvent); break; default: break; } } delete mouseEvent; } private: bool m_dragging; }; KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConverter* coordinatesConverter ,KisFavoriteResourceManager* manager, const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent) : QWidget(parent, Qt::FramelessWindowHint) , m_coordinatesConverter(coordinatesConverter) , m_viewManager(viewManager) , m_actionManager(viewManager->actionManager()) , m_resourceManager(manager) , m_displayRenderer(displayRenderer) , m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::POSTPONE)) , m_actionCollection(viewManager->actionCollection()) , m_acyclicConnector(new KisAcyclicSignalConnector(this)) , m_clicksEater(new KisMouseClickEater(Qt::RightButton, 1, this)) { // some UI controls are defined and created based off these variables const int borderWidth = 3; if (KisConfig(true).readEntry("popuppalette/usevisualcolorselector", false)) { KisVisualColorSelector *selector = new KisVisualColorSelector(this); selector->setAcceptTabletEvents(true); m_triangleColorSelector = selector; } else { m_triangleColorSelector = new PopupColorTriangle(displayRenderer, this); connect(m_triangleColorSelector, SIGNAL(requestCloseContainer()), this, SLOT(slotHide())); } m_triangleColorSelector->setDisplayRenderer(displayRenderer); m_triangleColorSelector->setConfig(true,false); m_triangleColorSelector->move(m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth, m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth); m_triangleColorSelector->resize(m_popupPaletteSize - 2*m_triangleColorSelector->x(), m_popupPaletteSize - 2*m_triangleColorSelector->y()); m_triangleColorSelector->setVisible(true); KoColor fgcolor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()); if (m_resourceManager) { fgcolor = provider->fgColor(); } m_triangleColorSelector->slotSetColor(fgcolor); /** * Tablet support code generates a spurious right-click right after opening * the window, so we should ignore it. Next right-click will be used for * closing the popup palette */ this->installEventFilter(m_clicksEater); m_triangleColorSelector->installEventFilter(m_clicksEater); QRegion maskedRegion(0, 0, m_triangleColorSelector->width(), m_triangleColorSelector->height(), QRegion::Ellipse ); m_triangleColorSelector->setMask(maskedRegion); //setAttribute(Qt::WA_TranslucentBackground, true); connect(m_triangleColorSelector, SIGNAL(sigNewColor(KoColor)), m_colorChangeCompressor.data(), SLOT(start())); connect(m_colorChangeCompressor.data(), SIGNAL(timeout()), SLOT(slotEmitColorChanged())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_triangleColorSelector, SLOT(configurationChanged())); connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), this, SLOT(slotDisplayConfigurationChanged())); m_acyclicConnector->connectForwardKoColor(m_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)), this, SLOT(slotExternalFgColorChanged(KoColor))); m_acyclicConnector->connectBackwardKoColor(this, SIGNAL(sigChangefGColor(KoColor)), m_resourceManager, SIGNAL(sigSetFGColor(KoColor))); connect(this, SIGNAL(sigChangeActivePaintop(int)), m_resourceManager, SLOT(slotChangeActivePaintop(int))); connect(this, SIGNAL(sigUpdateRecentColor(int)), m_resourceManager, SLOT(slotUpdateRecentColor(int))); connect(m_resourceManager, SIGNAL(setSelectedColor(int)), SLOT(slotSetSelectedColor(int))); connect(m_resourceManager, SIGNAL(updatePalettes()), SLOT(slotUpdate())); connect(m_resourceManager, SIGNAL(hidePalettes()), SLOT(slotHide())); setCursor(Qt::ArrowCursor); setMouseTracking(true); setHoveredPreset(-1); setHoveredColor(-1); setSelectedColor(-1); m_brushHud = new KisBrushHud(provider, parent); m_brushHud->setFixedHeight(int(m_popupPaletteSize)); m_brushHud->setVisible(false); const int auxButtonSize = 35; m_settingsButton = new KisRoundHudButton(this); m_settingsButton->setGeometry(m_popupPaletteSize - 2.2 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_settingsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup())); KisConfig cfg(true); m_brushHudButton = new KisRoundHudButton(this); m_brushHudButton->setCheckable(true); m_brushHudButton->setGeometry(m_popupPaletteSize - 1.0 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_brushHudButton, SIGNAL(toggled(bool)), SLOT(showHudWidget(bool))); m_brushHudButton->setChecked(cfg.showBrushHud()); // add some stuff below the pop-up palette that will make it easier to use for tablet people QVBoxLayout* vLayout = new QVBoxLayout(this); // main layout QSpacerItem* verticalSpacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding); vLayout->addSpacerItem(verticalSpacer); // this should push the box to the bottom QHBoxLayout* hLayout = new QHBoxLayout(); vLayout->addLayout(hLayout); mirrorMode = new KisHighlightedToolButton(this); mirrorMode->setFixedSize(35, 35); mirrorMode->setToolTip(i18n("Mirror Canvas")); mirrorMode->setDefaultAction(m_actionCollection->action("mirror_canvas")); canvasOnlyButton = new KisHighlightedToolButton(this); canvasOnlyButton->setFixedSize(35, 35); canvasOnlyButton->setToolTip(i18n("Canvas Only")); canvasOnlyButton->setDefaultAction(m_actionCollection->action("view_show_canvas_only")); zoomToOneHundredPercentButton = new QPushButton(this); zoomToOneHundredPercentButton->setText(i18n("100%")); zoomToOneHundredPercentButton->setFixedHeight(35); zoomToOneHundredPercentButton->setToolTip(i18n("Zoom to 100%")); connect(zoomToOneHundredPercentButton, SIGNAL(clicked(bool)), this, SLOT(slotZoomToOneHundredPercentClicked())); zoomCanvasSlider = new QSlider(Qt::Horizontal, this); zoomSliderMinValue = 10; // set in % zoomSliderMaxValue = 200; // set in % zoomCanvasSlider->setRange(zoomSliderMinValue, zoomSliderMaxValue); zoomCanvasSlider->setFixedHeight(35); zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); zoomCanvasSlider->setSingleStep(1); zoomCanvasSlider->setPageStep(1); connect(zoomCanvasSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int))); connect(zoomCanvasSlider, SIGNAL(sliderPressed()), this, SLOT(slotZoomSliderPressed())); connect(zoomCanvasSlider, SIGNAL(sliderReleased()), this, SLOT(slotZoomSliderReleased())); slotUpdateIcons(); hLayout->addWidget(mirrorMode); hLayout->addWidget(canvasOnlyButton); hLayout->addWidget(zoomToOneHundredPercentButton); hLayout->addWidget(zoomCanvasSlider); setVisible(true); setVisible(false); opacityChange = new QGraphicsOpacityEffect(this); setGraphicsEffect(opacityChange); // Prevent tablet events from being captured by the canvas setAttribute(Qt::WA_NoMousePropagation, true); } void KisPopupPalette::slotDisplayConfigurationChanged() { // Visual Color Selector picks up color space from input KoColor col = m_viewManager->canvasResourceProvider()->fgColor(); const KoColorSpace *paintingCS = m_displayRenderer->getPaintingColorSpace(); //hack to get around cmyk for now. if (paintingCS->colorChannelCount()>3) { paintingCS = KoColorSpaceRegistry::instance()->rgb8(); } m_triangleColorSelector->slotSetColorSpace(paintingCS); m_triangleColorSelector->slotSetColor(col); } void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color) { m_triangleColorSelector->slotSetColor(color); } void KisPopupPalette::slotEmitColorChanged() { if (isVisible()) { update(); emit sigChangefGColor(m_triangleColorSelector->getCurrentColor()); } } //setting KisPopupPalette properties int KisPopupPalette::hoveredPreset() const { return m_hoveredPreset; } void KisPopupPalette::setHoveredPreset(int x) { m_hoveredPreset = x; } int KisPopupPalette::hoveredColor() const { return m_hoveredColor; } void KisPopupPalette::setHoveredColor(int x) { m_hoveredColor = x; } int KisPopupPalette::selectedColor() const { return m_selectedColor; } void KisPopupPalette::setSelectedColor(int x) { m_selectedColor = x; } void KisPopupPalette::slotZoomSliderChanged(int zoom) { emit zoomLevelChanged(zoom); } void KisPopupPalette::slotZoomSliderPressed() { m_isZoomingCanvas = true; } void KisPopupPalette::slotZoomSliderReleased() { m_isZoomingCanvas = false; } void KisPopupPalette::adjustLayout(const QPoint &p) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); if (isVisible() && parentWidget()) { float hudMargin = 30.0; const QRect fitRect = kisGrowRect(parentWidget()->rect(), -20.0); // -20 is widget margin const QPoint paletteCenterOffset(m_popupPaletteSize / 2, m_popupPaletteSize / 2); QRect paletteRect = rect(); paletteRect.moveTo(p - paletteCenterOffset); if (m_brushHudButton->isChecked()) { m_brushHud->updateGeometry(); paletteRect.adjust(0, 0, m_brushHud->width() + hudMargin, 0); } paletteRect = kisEnsureInRect(paletteRect, fitRect); move(paletteRect.topLeft()); m_brushHud->move(paletteRect.topLeft() + QPoint(m_popupPaletteSize + hudMargin, 0)); m_lastCenterPoint = p; } } void KisPopupPalette::slotUpdateIcons() { this->setPalette(qApp->palette()); for(int i=0; ichildren().size(); i++) { QWidget *w = qobject_cast(this->children().at(i)); if (w) { w->setPalette(qApp->palette()); } } zoomToOneHundredPercentButton->setIcon(m_actionCollection->action("zoom_to_100pct")->icon()); m_brushHud->updateIcons(); m_settingsButton->setIcon(KisIconUtils::loadIcon("tag")); m_brushHudButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-left"), KisIconUtils::loadIcon("arrow-right")); } void KisPopupPalette::showHudWidget(bool visible) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); const bool reallyVisible = visible && m_brushHudButton->isChecked(); if (reallyVisible) { m_brushHud->updateProperties(); } m_brushHud->setVisible(reallyVisible); adjustLayout(m_lastCenterPoint); KisConfig cfg(false); cfg.setShowBrushHud(visible); } void KisPopupPalette::showPopupPalette(const QPoint &p) { showPopupPalette(!isVisible()); adjustLayout(p); } void KisPopupPalette::showPopupPalette(bool show) { if (show) { // don't set the zoom slider if we are outside of the zoom slider bounds. It will change the zoom level to within // the bounds and cause the canvas to jump between the slider's min and max if (m_coordinatesConverter->zoomInPercent() > zoomSliderMinValue && m_coordinatesConverter->zoomInPercent() < zoomSliderMaxValue ){ KisSignalsBlocker b(zoomCanvasSlider); zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider } } setVisible(show); m_brushHud->setVisible(show && m_brushHudButton->isChecked()); } //redefinition of setVariable function to change the scope to private void KisPopupPalette::setVisible(bool b) { QWidget::setVisible(b); } void KisPopupPalette::setParent(QWidget *parent) { m_brushHud->setParent(parent); QWidget::setParent(parent); } QSize KisPopupPalette::sizeHint() const { return QSize(m_popupPaletteSize, m_popupPaletteSize + 50); // last number is the space for the toolbar below } void KisPopupPalette::resizeEvent(QResizeEvent*) { } void KisPopupPalette::paintEvent(QPaintEvent* e) { Q_UNUSED(e); QPainter painter(this); QPen pen(palette().color(QPalette::Text)); pen.setWidth(3); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::SmoothPixmapTransform); // painting background color indicator QPainterPath bgColor; bgColor.addEllipse(QPoint( 50, 80), 30, 30); painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor())); painter.drawPath(bgColor); // painting foreground color indicator QPainterPath fgColor; fgColor.addEllipse(QPoint( 60, 50), 30, 30); painter.fillPath(fgColor, m_displayRenderer->toQColor(m_triangleColorSelector->getCurrentColor())); painter.drawPath(fgColor); // create a circle background that everything else will go into QPainterPath backgroundContainer; float shrinkCircleAmount = 3;// helps the circle when the stroke is put around it QRectF circleRect(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); backgroundContainer.addEllipse( circleRect ); painter.fillPath(backgroundContainer,palette().brush(QPalette::Background)); painter.drawPath(backgroundContainer); // create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas // with the indicator QPainterPath rotationTrackPath; shrinkCircleAmount = 18; QRectF circleRect2(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); rotationTrackPath.addEllipse( circleRect2 ); pen.setWidth(1); painter.setPen(pen); painter.drawPath(rotationTrackPath); // this thing will help indicate where the starting brush preset is at. // also what direction they go to give sor order to the presets populated /* pen.setWidth(6); pen.setCapStyle(Qt::RoundCap); painter.setPen(pen); painter.drawArc(circleRect, (16*90), (16*-30)); // span angle (last parameter) is in 16th of degrees QPainterPath brushDir; brushDir.arcMoveTo(circleRect, 60); brushDir.lineTo(brushDir.currentPosition().x()-5, brushDir.currentPosition().y() - 14); painter.drawPath(brushDir); brushDir.lineTo(brushDir.currentPosition().x()-2, brushDir.currentPosition().y() + 6); painter.drawPath(brushDir); */ // the following things needs to be based off the center, so let's translate the painter painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2); // create the canvas rotation handle QPainterPath rotationIndicator = drawRotationIndicator(m_coordinatesConverter->rotationAngle(), true); painter.fillPath(rotationIndicator,palette().brush(QPalette::Text)); // hover indicator for the canvas rotation if (m_isOverCanvasRotationIndicator == true) { painter.save(); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(2); painter.setPen(pen); painter.drawPath(rotationIndicator); painter.restore(); } // create a reset canvas rotation indicator to bring the canvas back to 0 degrees QPainterPath resetRotationIndicator = drawRotationIndicator(0, false); QPen resetPen(palette().color(QPalette::Text)); resetPen.setWidth(1); painter.save(); painter.setPen(resetPen); painter.drawPath(resetRotationIndicator); painter.restore(); // painting favorite brushes QList images(m_resourceManager->favoritePresetImages()); // painting favorite brushes pixmap/icon QPainterPath presetPath; for (int pos = 0; pos < numSlots(); pos++) { painter.save(); presetPath = createPathFromPresetIndex(pos); if (pos < images.size()) { painter.setClipPath(presetPath); QRect bounds = presetPath.boundingRect().toAlignedRect(); painter.drawImage(bounds.topLeft() , images.at(pos).scaled(bounds.size() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); } else { painter.fillPath(presetPath, palette().brush(QPalette::Window)); // brush slot that has no brush in it } QPen pen = painter.pen(); pen.setWidth(1); painter.setPen(pen); painter.drawPath(presetPath); painter.restore(); } if (hoveredPreset() > -1) { presetPath = createPathFromPresetIndex(hoveredPreset()); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(3); painter.setPen(pen); painter.drawPath(presetPath); } // paint recent colors area. painter.setPen(Qt::NoPen); float rotationAngle = -360.0 / m_resourceManager->recentColorsTotal(); // there might be no recent colors at the start, so paint a placeholder if (m_resourceManager->recentColorsTotal() == 0) { painter.setBrush(Qt::transparent); QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.setPen(QPen(palette().color(QPalette::Background).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); painter.drawPath(emptyRecentColorsPath); } else { for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) { QPainterPath recentColorsPath(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); //accessing recent color of index pos painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) )); painter.drawPath(recentColorsPath); painter.rotate(rotationAngle); } } // painting hovered color if (hoveredColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(hoveredColor() * -1 * rotationAngle); } } // painting selected color if (selectedColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(selectedColor() * -1 * rotationAngle); } } // if we are actively rotating the canvas or zooming, make the panel slightly transparent to see the canvas better if(m_isRotatingCanvasIndicator || m_isZoomingCanvas) { opacityChange->setOpacity(0.4); } else { opacityChange->setOpacity(1.0); } } QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius) { QPainterPath path; path.addEllipse(QPointF(x, y), outer_radius, outer_radius); path.addEllipse(QPointF(x, y), inner_radius, inner_radius); path.setFillRule(Qt::OddEvenFill); return path; } QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit) { QPainterPath path; path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit)); path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit, 360.0 / limit); path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit, - 360.0 / limit); path.closeSubpath(); return path; } QPainterPath KisPopupPalette::drawRotationIndicator(qreal rotationAngle, bool canDrag) { // used for canvas rotation. This function gets called twice. Once by the canvas rotation indicator, // and another time by the reset canvas position float canvasRotationRadians = qDegreesToRadians(rotationAngle - 90); // -90 will make 0 degrees be at the top float rotationDialXPosition = qCos(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); // m_popupPaletteSize/2 = radius float rotationDialYPosition = qSin(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); QPainterPath canvasRotationIndicator; int canvasIndicatorSize = 15; int canvasIndicatorMiddle = canvasIndicatorSize / 2; QRect indicatorRectangle = QRect( rotationDialXPosition - canvasIndicatorMiddle, rotationDialYPosition - canvasIndicatorMiddle, canvasIndicatorSize, canvasIndicatorSize ); if (canDrag) { m_canvasRotationIndicatorRect = indicatorRectangle; } else { m_resetCanvasRotationIndicatorRect = indicatorRectangle; } canvasRotationIndicator.addEllipse(indicatorRectangle.x(), indicatorRectangle.y(), indicatorRectangle.width(), indicatorRectangle.height() ); return canvasRotationIndicator; } void KisPopupPalette::mouseMoveEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); setToolTip(QString()); setHoveredPreset(-1); setHoveredColor(-1); // calculate if we are over the canvas rotation knob // before we started painting, we moved the painter to the center of the widget, so the X/Y positions are offset. we need to // correct them first before looking for a click event intersection float rotationCorrectedXPos = m_canvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_canvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_canvasRotationIndicatorRect.width(), m_canvasRotationIndicatorRect.height()); if (correctedCanvasRotationIndicator.contains(point.x(), point.y())) { m_isOverCanvasRotationIndicator = true; } else { m_isOverCanvasRotationIndicator = false; } if (m_isRotatingCanvasIndicator) { // we are rotating the canvas, so calculate the rotation angle based off the center // calculate the angle we are at first QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2); float dX = point.x() - widgetCenterPoint.x(); float dY = point.y() - widgetCenterPoint.y(); float finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up float angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out KisCanvasController *canvasController = dynamic_cast(m_viewManager->canvasBase()->canvasController()); canvasController->rotateCanvas(angleDifference); emit sigUpdateCanvas(); } // don't highlight the presets if we are in the middle of rotating the canvas if (m_isRotatingCanvasIndicator == false) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); { int pos = calculatePresetIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) { setToolTip(m_resourceManager->favoritePresetNamesList().at(pos)); setHoveredPreset(pos); } } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { setHoveredColor(pos); } } } update(); } void KisPopupPalette::mousePressEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); if (event->button() == Qt::LeftButton) { //in favorite brushes area int pos = calculateIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets() && isPointInPixmap(point, pos)) { //setSelectedBrush(pos); update(); } if (m_isOverCanvasRotationIndicator) { m_isRotatingCanvasIndicator = true; } // reset the canvas if we are over the reset canvas rotation indicator float rotationCorrectedXPos = m_resetCanvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_resetCanvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedResetCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_resetCanvasRotationIndicatorRect.width(), m_resetCanvasRotationIndicatorRect.height()); if (correctedResetCanvasRotationIndicator.contains(point.x(), point.y())) { float angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs KisCanvasController *canvasController = dynamic_cast(m_viewManager->canvasBase()->canvasController()); canvasController->rotateCanvas(angleDifference); emit sigUpdateCanvas(); } } } void KisPopupPalette::slotShowTagsPopup() { KisTagModel *model = KisTagModelProvider::tagModel(ResourceType::PaintOpPresets); QVector tags; for (int i = 0; i < model->rowCount(); ++i) { QModelIndex idx = model->index(i, 0); tags << model->data(idx, Qt::DisplayRole).toString(); } //std::sort(tags.begin(), tags.end()); if (!tags.isEmpty()) { QMenu menu; Q_FOREACH (const QString& tag, tags) { menu.addAction(tag); } QAction *action = menu.exec(QCursor::pos()); if (action) { for (int i = 0; i < model->rowCount(); ++i) { QModelIndex idx = model->index(i, 0); if (model->data(idx, Qt::DisplayRole).toString() == action->text()) { m_resourceManager->setCurrentTag(model->tagForIndex(idx)); break; } } } } else { QWhatsThis::showText(QCursor::pos(), i18n("There are no tags available to show in this popup. To add presets, you need to tag them and then select the tag here.")); } } void KisPopupPalette::slotZoomToOneHundredPercentClicked() { QAction *action = m_actionCollection->action("zoom_to_100pct"); if (action) { action->trigger(); } // also move the zoom slider to 100% position so they are in sync zoomCanvasSlider->setValue(100); } void KisPopupPalette::tabletEvent(QTabletEvent *event) { event->ignore(); } void KisPopupPalette::showEvent(QShowEvent *event) { m_clicksEater->reset(); QWidget::showEvent(event); } void KisPopupPalette::mouseReleaseEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); if (event->buttons() == Qt::NoButton && event->button() == Qt::RightButton) { showPopupPalette(false); return; } m_isOverCanvasRotationIndicator = false; m_isRotatingCanvasIndicator = false; if (event->button() == Qt::LeftButton) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); //in favorite brushes area if (hoveredPreset() > -1) { //setSelectedBrush(hoveredBrush()); emit sigChangeActivePaintop(hoveredPreset()); } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { emit sigUpdateRecentColor(pos); } } } } int KisPopupPalette::calculateIndex(QPointF point, int n) { calculatePresetIndex(point, n); //translate to (0,0) point.setX(point.x() - m_popupPaletteSize / 2); point.setY(point.y() - m_popupPaletteSize / 2); //rotate float smallerAngle = M_PI / 2 + M_PI / n - atan2(point.y(), point.x()); float radius = sqrt((float)point.x() * point.x() + point.y() * point.y()); point.setX(radius * cos(smallerAngle)); point.setY(radius * sin(smallerAngle)); //calculate brush index int pos = floor(acos(point.x() / radius) * n / (2 * M_PI)); if (point.y() < 0) pos = n - pos - 1; return pos; } bool KisPopupPalette::isPointInPixmap(QPointF &point, int pos) { if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) { return true; } return false; } KisPopupPalette::~KisPopupPalette() { } QPainterPath KisPopupPalette::createPathFromPresetIndex(int index) { qreal angleSlice = 360.0 / numSlots() ; // how many degrees each slice will get // the starting angle of the slice we need to draw. the negative sign makes us go clockwise. // adding 90 degrees makes us start at the top. otherwise we would start at the right qreal startingAngle = -(index * angleSlice) + 90; // the radius will get smaller as the amount of presets shown increases. 10 slots == 41 qreal radians = qDegreesToRadians((360.0/10)/2); qreal maxRadius = (m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians)))-2; radians = qDegreesToRadians(angleSlice/2); qreal presetRadius = m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians)); //If we assume that circles will mesh like a hexagonal grid, then 3.5r is the size of two hexagons interlocking. qreal length = m_colorHistoryOuterRadius + presetRadius; // can we can fit in a second row? We don't want the preset icons to get too tiny. if (maxRadius > presetRadius) { //redo all calculations assuming a second row. if (numSlots() % 2) { angleSlice = 360.0/(numSlots()+1); startingAngle = -(index * angleSlice) + 90; } if (numSlots() != m_cachedNumSlots){ qreal tempRadius = presetRadius; qreal distance = 0; do{ tempRadius+=0.1; // Calculate the XY of two adjectant circles using this tempRadius. qreal length1 = m_colorHistoryOuterRadius + tempRadius; qreal length2 = m_colorHistoryOuterRadius + ((maxRadius*2)-tempRadius); qreal pathX1 = length1 * qCos(qDegreesToRadians(startingAngle)) - tempRadius; qreal pathY1 = -(length1) * qSin(qDegreesToRadians(startingAngle)) - tempRadius; qreal startingAngle2 = -(index+1 * angleSlice) + 90; qreal pathX2 = length2 * qCos(qDegreesToRadians(startingAngle2)) - tempRadius; qreal pathY2 = -(length2) * qSin(qDegreesToRadians(startingAngle2)) - tempRadius; // Use Pythagorean Theorem to calculate the distance between these two values. qreal m1 = pathX2-pathX1; qreal m2 = pathY2-pathY1; distance = sqrt((m1*m1)+(m2*m2)); } //As long at there's more distance than the radius of the two presets, continue increasing the radius. while((tempRadius+1)*2 < distance); m_cachedRadius = tempRadius; } m_cachedNumSlots = numSlots(); presetRadius = m_cachedRadius; length = m_colorHistoryOuterRadius + presetRadius; if (index % 2) { length = m_colorHistoryOuterRadius + ((maxRadius*2)-presetRadius); } } QPainterPath path; qreal pathX = length * qCos(qDegreesToRadians(startingAngle)) - presetRadius; qreal pathY = -(length) * qSin(qDegreesToRadians(startingAngle)) - presetRadius; qreal pathDiameter = 2 * presetRadius; // distance is used to calculate the X/Y in addition to the preset circle size path.addEllipse(pathX, pathY, pathDiameter, pathDiameter); return path; } int KisPopupPalette::calculatePresetIndex(QPointF point, int /*n*/) { for(int i = 0; i < numSlots(); i++) { QPointF adujustedPoint = point - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2); if(createPathFromPresetIndex(i).contains(adujustedPoint)) { return i; } } return -1; } int KisPopupPalette::numSlots() { KisConfig config(true); return qMax(config.favoritePresets(), 10); } diff --git a/libs/ui/widgets/kis_layer_filter_widget.cpp b/libs/ui/widgets/kis_layer_filter_widget.cpp index 1310e5ab17..328b7ab956 100644 --- a/libs/ui/widgets/kis_layer_filter_widget.cpp +++ b/libs/ui/widgets/kis_layer_filter_widget.cpp @@ -1,301 +1,302 @@ /* * Copyright (c) 2020 Eoin O'Neill * Copyright (c) 2020 Emmet O'Neill * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_filter_widget.h" +#include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "kis_node.h" #include "kis_global.h" #include "kis_icon_utils.h" #include "kis_color_filter_combo.h" #include "kis_color_label_button.h" #include "kis_color_label_selector_widget.h" #include "kis_node_view_color_scheme.h" #include "KisMouseClickEater.h" KisLayerFilterWidget::KisLayerFilterWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *layout = new QVBoxLayout(this); setLayout(layout); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); textFilter = new QLineEdit(this); textFilter->setPlaceholderText(i18n("Filter by name...")); textFilter->setMinimumWidth(255); textFilter->setMinimumHeight(28); textFilter->setClearButtonEnabled(true); connect(textFilter, SIGNAL(textChanged(QString)), this, SIGNAL(filteringOptionsChanged())); connect(textFilter, &QLineEdit::returnPressed, [this]() { QMenu* menu = dynamic_cast(parentWidget()); if (menu) { menu->close(); } }); KisNodeViewColorScheme colorScheme; QWidget* buttonContainer = new QWidget(this); MouseClickIgnore* mouseEater = new MouseClickIgnore(this); buttonContainer->setToolTip(i18n("Filter by color label...")); buttonContainer->installEventFilter(mouseEater); buttonEventFilter = new KisColorLabelMouseDragFilter(buttonContainer); { QHBoxLayout *subLayout = new QHBoxLayout(buttonContainer); buttonContainer->setLayout(subLayout); subLayout->setContentsMargins(0,0,0,0); subLayout->setSpacing(0); subLayout->setAlignment(Qt::AlignLeft); buttonGroup = new KisColorLabelFilterGroup(buttonContainer); buttonGroup->setExclusive(false); QVector colors = colorScheme.allColorLabels(); for (int id = 0; id < colors.count(); id++) { KisColorLabelButton* btn = new KisColorLabelButton(colors[id], 28, buttonContainer); buttonGroup->addButton(btn, id); btn->installEventFilter(buttonEventFilter); subLayout->addWidget(btn); } connect(buttonGroup, SIGNAL(buttonToggled(int,bool)), this, SIGNAL(filteringOptionsChanged())); } resetButton = new QPushButton(i18n("Reset Filters"), this); resetButton->setMinimumHeight(28); connect(resetButton, &QPushButton::clicked, [this](){ this->reset(); }); layout->addWidget(textFilter); layout->addWidget(buttonContainer); layout->addWidget(resetButton); } void KisLayerFilterWidget::scanUsedColorLabels(KisNodeSP node, QSet &colorLabels) { if (node->parent()) { colorLabels.insert(node->colorLabelIndex()); } KisNodeSP child = node->firstChild(); while(child) { scanUsedColorLabels(child, colorLabels); child = child->nextSibling(); } } void KisLayerFilterWidget::updateColorLabels(KisNodeSP root) { QSet colorLabels; scanUsedColorLabels(root, colorLabels); buttonGroup->setViableLabels(colorLabels); } bool KisLayerFilterWidget::isCurrentlyFiltering() const { const bool isFilteringText = hasTextFilter(); const bool isFilteringColors = buttonGroup->getActiveLabels().count() > 0; return isFilteringText || isFilteringColors; } bool KisLayerFilterWidget::hasTextFilter() const { return !textFilter->text().isEmpty(); } QSet KisLayerFilterWidget::getActiveColors() const { QSet activeColors = buttonGroup->getActiveLabels(); return activeColors; } QString KisLayerFilterWidget::getTextFilter() const { return textFilter->text(); } int KisLayerFilterWidget::getDesiredMinimumWidth() const { return qMax(textFilter->minimumWidth(), buttonGroup->countViableButtons() * 32); } int KisLayerFilterWidget::getDesiredMinimumHeight() const { QList viableButtons = buttonGroup->viableButtons(); if (viableButtons.count() > 1) { return viableButtons[0]->sizeHint().height() + textFilter->minimumHeight() + resetButton->minimumHeight(); } else { return textFilter->minimumHeight() + resetButton->minimumHeight(); } } void KisLayerFilterWidget::reset() { textFilter->clear(); buttonGroup->reset(); filteringOptionsChanged(); } QSize KisLayerFilterWidget::sizeHint() const { return QSize(getDesiredMinimumWidth(), getDesiredMinimumHeight()); } void KisLayerFilterWidget::showEvent(QShowEvent *show) { QMenu *parentMenu = dynamic_cast(parentWidget()); if (parentMenu) { const int widthBefore = parentMenu->width(); const int rightEdgeThreshold = 5; //Fake resize event needs to be made to register change in widget menu size. //Not doing this will cause QMenu to not resize properly! resize(sizeHint()); adjustSize(); QResizeEvent event = QResizeEvent(sizeHint(), parentMenu->size()); parentMenu->resize(sizeHint()); parentMenu->adjustSize(); qApp->sendEvent(parentMenu, &event); #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) QScreen *screen = QGuiApplication::screenAt(parentMenu->mapToGlobal(parentMenu->pos())); QRect screenGeometry = screen ? screen->geometry() : parentMenu->parentWidget()->window()->geometry(); #else QRect screenGeometry = QApplication::desktop()->screenGeometry(this); #endif const bool onRightEdge = (parentMenu->pos().x() + widthBefore + rightEdgeThreshold) > screenGeometry.width(); const int widthAfter = parentMenu->width(); if (onRightEdge) { if (widthAfter > widthBefore) { const QRect newGeo = kisEnsureInRect( parentMenu->geometry(), screenGeometry ); const int xShift = newGeo.x() - parentMenu->pos().x(); parentMenu->move(parentMenu->pos().x() + xShift, parentMenu->pos().y() + 0); } else { const int xShift = widthBefore - widthAfter; parentMenu->move(parentMenu->pos().x() + xShift, parentMenu->pos().y() + 0); } } } QWidget::showEvent(show); } KisLayerFilterWidgetToolButton::KisLayerFilterWidgetToolButton(QWidget *parent) : QToolButton(parent) { m_textFilter = false; m_selectedColors = QList(); } KisLayerFilterWidgetToolButton::KisLayerFilterWidgetToolButton(const KisLayerFilterWidgetToolButton &rhs) : QToolButton(rhs.parentWidget()) , m_textFilter(rhs.m_textFilter) , m_selectedColors(rhs.m_selectedColors) { } void KisLayerFilterWidgetToolButton::setSelectedColors(QList colors) { m_selectedColors = colors; } void KisLayerFilterWidgetToolButton::setTextFilter(bool isTextFiltering) { m_textFilter = isTextFiltering; } void KisLayerFilterWidgetToolButton::paintEvent(QPaintEvent *paintEvent) { KisNodeViewColorScheme colorScheme; const bool validColorFilter = !(m_selectedColors.count() == 0 || m_selectedColors.count() == colorScheme.allColorLabels().count()); if (m_textFilter == false && !validColorFilter) { QToolButton::paintEvent(paintEvent); } else { QStylePainter paint(this); QStyleOptionToolButton opt; initStyleOption(&opt); opt.icon = m_textFilter ? KisIconUtils::loadIcon("format-text-bold") : icon(); paint.drawComplexControl(QStyle::CC_ToolButton, opt); const QSize halfIconSize = this->iconSize() / 2; const QSize halfButtonSize = this->size() / 2; const QRect editRect = kisGrowRect(QRect(QPoint(halfButtonSize.width() - halfIconSize.width(), halfButtonSize.height() - halfIconSize.height()),this->iconSize()), -1); const int size = qMin(editRect.width(), editRect.height()); if( validColorFilter ) { KisColorFilterCombo::paintColorPie(paint, opt.palette, m_selectedColors, editRect, size ); if (m_textFilter) { if (!opt.icon.isNull()) { QRadialGradient radGradient = QRadialGradient(editRect.center(), size); QColor shadowTransparent = palette().shadow().color(); shadowTransparent.setAlpha(96); radGradient.setColorAt(0.0f, shadowTransparent); shadowTransparent.setAlpha(0); radGradient.setColorAt(1.0f, shadowTransparent); paint.setBrush(radGradient); paint.setPen(Qt::NoPen); paint.drawEllipse(editRect.center(), size, size); opt.icon.paint(&paint, editRect); } } } } } MouseClickIgnore::MouseClickIgnore(QObject *parent) : QObject(parent) { } bool MouseClickIgnore::eventFilter(QObject *obj, QEvent *event) { if (obj && (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick || event->type() == QEvent::MouseButtonRelease)) { event->setAccepted(true); return true; } else { return false; } } diff --git a/libs/widgets/KisVisualColorSelector.cpp b/libs/widgets/KisVisualColorSelector.cpp index ee3b4720c1..21047d14e8 100644 --- a/libs/widgets/KisVisualColorSelector.cpp +++ b/libs/widgets/KisVisualColorSelector.cpp @@ -1,657 +1,656 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisVisualColorSelector.h" -#include -#include -#include -#include #include #include #include #include #include #include -#include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoColorProfile.h" #include "KoChannelInfo.h" #include #include #include "kis_signal_compressor.h" #include "kis_debug.h" #include "KisVisualColorSelectorShape.h" #include "KisVisualRectangleSelectorShape.h" #include "KisVisualTriangleSelectorShape.h" #include "KisVisualEllipticalSelectorShape.h" struct KisVisualColorSelector::Private { KoColor currentcolor; const KoColorSpace *currentCS {0}; QList widgetlist; bool acceptTabletEvents {false}; bool circular {false}; bool exposureSupported {false}; bool isRGBA {false}; bool isLinear {false}; bool applyGamma {false}; int displayPosition[4]; // map channel index to storage index for display int colorChannelCount {0}; qreal gamma {2.2}; qreal lumaRGB[3] {0.2126, 0.7152, 0.0722}; QVector4D channelValues; QVector4D channelMaxValues; ColorModel model {ColorModel::None}; const KoColorDisplayRendererInterface *displayRenderer {0}; KisColorSelectorConfiguration acs_config; KisSignalCompressor *updateTimer {0}; }; KisVisualColorSelector::KisVisualColorSelector(QWidget *parent) : KisColorSelectorInterface(parent) , m_d(new Private) { this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString())); m_d->updateTimer = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE); connect(m_d->updateTimer, SIGNAL(timeout()), SLOT(slotRebuildSelectors()), Qt::UniqueConnection); } KisVisualColorSelector::~KisVisualColorSelector() { delete m_d->updateTimer; } void KisVisualColorSelector::slotSetColor(const KoColor &c) { if (!m_d->currentCS) { m_d->currentcolor = c; slotSetColorSpace(c.colorSpace()); } else { m_d->currentcolor = c.convertedTo(m_d->currentCS); m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor); Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setChannelValues(m_d->channelValues, true); } } if (isHSXModel()) { emit sigHSXChanged(QVector3D(m_d->channelValues)); } } void KisVisualColorSelector::slotSetColorSpace(const KoColorSpace *cs) { if (!m_d->currentCS || *m_d->currentCS != *cs) { const KoColorSpace *csNew = cs; // PQ color space is not very suitable for color picking, substitute with linear one if (cs->colorModelId() == RGBAColorModelID && cs->profile()->uniqueId() == KoColorSpaceRegistry::instance()->p2020PQProfile()->uniqueId()) { csNew = KoColorSpaceRegistry::instance()-> colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020G10Profile()); } m_d->currentCS = csNew; m_d->currentcolor = KoColor(csNew); slotRebuildSelectors(); } } void KisVisualColorSelector::slotSetHSX(const QVector3D &hsx) { if (isHSXModel()) { m_d->channelValues = QVector4D(hsx, 0.f); Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setChannelValues(m_d->channelValues, true); } KoColor newColor = convertShapeCoordsToKoColor(QVector4D(hsx)); if (newColor != m_d->currentcolor) { m_d->currentcolor = newColor; emit sigNewColor(m_d->currentcolor); } } } void KisVisualColorSelector::setConfig(bool forceCircular, bool forceSelfUpdate) { Q_UNUSED(forceSelfUpdate) m_d->circular = forceCircular; } void KisVisualColorSelector::setAcceptTabletEvents(bool on) { m_d->acceptTabletEvents = on; Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setAcceptTabletEvents(on); } } KoColor KisVisualColorSelector::getCurrentColor() const { return m_d->currentcolor; } QVector4D KisVisualColorSelector::getChannelValues() const { return m_d->channelValues; } KisVisualColorSelector::ColorModel KisVisualColorSelector::getColorModel() const { return m_d->model; } bool KisVisualColorSelector::isHSXModel() const { return (m_d->model >= ColorModel::HSV && m_d->model <= ColorModel::HSY); } KoColor KisVisualColorSelector::convertShapeCoordsToKoColor(const QVector4D &coordinates) const { KoColor c(m_d->currentCS); QVector4D baseValues(coordinates); QVector channelValues(c.colorSpace()->channelCount()); channelValues.fill(1.0); if (m_d->model != ColorModel::Channel && m_d->isRGBA == true) { if (m_d->model == ColorModel::HSV) { HSVToRGB(coordinates.x()*360, coordinates.y(), coordinates.z(), &baseValues[0], &baseValues[1], &baseValues[2]); } else if (m_d->model == ColorModel::HSL) { HSLToRGB(coordinates.x()*360, coordinates.y(), coordinates.z(), &baseValues[0], &baseValues[1], &baseValues[2]); } else if (m_d->model == ColorModel::HSI) { // why suddenly qreal? qreal temp[3]; HSIToRGB(coordinates.x(), coordinates.y(), coordinates.z(), &temp[0], &temp[1], &temp[2]); baseValues.setX(temp[0]); baseValues.setY(temp[1]); baseValues.setZ(temp[2]); } else /*if (m_d->model == ColorModel::HSY)*/ { qreal temp[3]; qreal Y = pow(coordinates.z(), m_d->gamma); HSYToRGB(coordinates.x(), coordinates.y(), Y, &temp[0], &temp[1], &temp[2], m_d->lumaRGB[0], m_d->lumaRGB[1], m_d->lumaRGB[2]); baseValues.setX(temp[0]); baseValues.setY(temp[1]); baseValues.setZ(temp[2]); if (!m_d->isLinear) { // Note: not all profiles define a TRC necessary for (de-)linearization, // substituting with a linear profiles would be better QVector temp({baseValues[0], baseValues[1], baseValues[2]}); if (m_d->exposureSupported) { m_d->currentCS->profile()->delinearizeFloatValue(temp); } else { m_d->currentCS->profile()->delinearizeFloatValueFast(temp); } baseValues = QVector4D(temp[0], temp[1], temp[2], 0); } } if (m_d->applyGamma) { for (int i=0; i<3; i++) { baseValues[i] = pow(baseValues[i], 2.2); } } } if (m_d->exposureSupported) { baseValues *= m_d->channelMaxValues; } for (int i=0; icolorChannelCount; i++) { channelValues[m_d->displayPosition[i]] = baseValues[i]; } c.colorSpace()->fromNormalisedChannelsValue(c.data(), channelValues); return c; } QVector4D KisVisualColorSelector::convertKoColorToShapeCoordinates(KoColor c) const { if (c.colorSpace() != m_d->currentCS) { c.convertTo(m_d->currentCS); } QVector channelValues (c.colorSpace()->channelCount()); channelValues.fill(1.0); m_d->currentCS->normalisedChannelsValue(c.data(), channelValues); QVector4D channelValuesDisplay(0, 0, 0, 0), coordinates(0, 0, 0, 0); for (int i =0; icolorChannelCount; i++) { channelValuesDisplay[i] = channelValues[m_d->displayPosition[i]]; } if (m_d->exposureSupported) { channelValuesDisplay /= m_d->channelMaxValues; } if (m_d->model != ColorModel::Channel && m_d->isRGBA == true) { if (m_d->isRGBA == true) { if (m_d->applyGamma) { for (int i=0; i<3; i++) { channelValuesDisplay[i] = pow(channelValuesDisplay[i], 1/2.2); } } if (m_d->model == ColorModel::HSV) { QVector3D hsv; RGBToHSV(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsv[0], &hsv[1], &hsv[2]); hsv[0] /= 360; coordinates = QVector4D(hsv, 0.f); } else if (m_d->model == ColorModel::HSL) { QVector3D hsl; RGBToHSL(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsl[0], &hsl[1], &hsl[2]); hsl[0] /= 360; coordinates = QVector4D(hsl, 0.f); } else if (m_d->model == ColorModel::HSI) { qreal hsi[3]; RGBToHSI(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsi[0], &hsi[1], &hsi[2]); coordinates = QVector4D(hsi[0], hsi[1], hsi[2], 0.f); } else if (m_d->model == ColorModel::HSY) { if (!m_d->isLinear) { // Note: not all profiles define a TRC necessary for (de-)linearization, // substituting with a linear profiles would be better QVector temp({channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2]}); m_d->currentCS->profile()->linearizeFloatValue(temp); channelValuesDisplay = QVector4D(temp[0], temp[1], temp[2], 0); } qreal hsy[3]; RGBToHSY(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsy[0], &hsy[1], &hsy[2], m_d->lumaRGB[0], m_d->lumaRGB[1], m_d->lumaRGB[2]); hsy[2] = pow(hsy[2], 1/m_d->gamma); coordinates = QVector4D(hsy[0], hsy[1], hsy[2], 0.f); } // if we couldn't determine a hue, keep last value if (coordinates[0] < 0) { coordinates[0] = m_d->channelValues[0]; } for (int i=0; i<3; i++) { coordinates[i] = qBound(0.f, coordinates[i], 1.f); } } } else { for (int i=0; i<4; i++) { coordinates[i] = qBound(0.f, channelValuesDisplay[i], 1.f); } } return coordinates; } void KisVisualColorSelector::configurationChanged() { if (m_d->updateTimer) { m_d->updateTimer->start(); } } void KisVisualColorSelector::slotDisplayConfigurationChanged() { Q_ASSERT(m_d->displayRenderer); if (m_d->currentCS) { m_d->channelMaxValues = QVector4D(1, 1, 1, 1); QList channels = m_d->currentCS->channels(); for (int i=0; icolorChannelCount; ++i) { m_d->channelMaxValues[i] = m_d->displayRenderer->maxVisibleFloatValue(channels[m_d->displayPosition[i]]); } // need to re-scale our normalized channel values on exposure changes: m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor); Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setChannelValues(m_d->channelValues, true); } if (isHSXModel()) { emit sigHSXChanged(QVector3D(m_d->channelValues)); } } } void KisVisualColorSelector::slotRebuildSelectors() { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString())); ColorModel oldModel = m_d->model; QList channelList = m_d->currentCS->channels(); int cCount = 0; Q_FOREACH(const KoChannelInfo *channel, channelList) { if (channel->channelType() != KoChannelInfo::ALPHA) { m_d->displayPosition[cCount] = channel->displayPosition(); ++cCount; } } Q_ASSERT_X(cCount < 5, "", "unsupported channel count!"); m_d->colorChannelCount = cCount; // TODO: The following is done because the IDs are actually strings. Ideally, in the future, we // refactor everything so that the IDs are actually proper enums or something faster. if (m_d->displayRenderer && (m_d->currentCS->colorDepthId() == Float16BitsColorDepthID || m_d->currentCS->colorDepthId() == Float32BitsColorDepthID || m_d->currentCS->colorDepthId() == Float64BitsColorDepthID) && m_d->currentCS->colorModelId() != LABAColorModelID && m_d->currentCS->colorModelId() != CMYKAColorModelID) { m_d->exposureSupported = true; } else { m_d->exposureSupported = false; } m_d->isRGBA = (m_d->currentCS->colorModelId() == RGBAColorModelID); const KoColorProfile *profile = m_d->currentCS->profile(); m_d->isLinear = (profile && profile->isLinear()); qDeleteAll(children()); m_d->widgetlist.clear(); // TODO: Layout only used for monochrome selector currently, but always present QLayout *layout = new QHBoxLayout; //recreate all the widgets. m_d->model = KisVisualColorSelector::Channel; if (m_d->currentCS->colorChannelCount() == 1) { KisVisualColorSelectorShape *bar; if (m_d->circular==false) { bar = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, 0, 0,m_d->displayRenderer, 20); bar->setMaximumWidth(width()*0.1); bar->setMaximumHeight(height()); } else { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, 0, 0,m_d->displayRenderer, 20, KisVisualEllipticalSelectorShape::borderMirrored); layout->setMargin(0); } connect(bar, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); layout->addWidget(bar); m_d->widgetlist.append(bar); } else if (m_d->currentCS->colorChannelCount() == 3) { KisVisualColorSelector::ColorModel modelS = KisVisualColorSelector::HSV; int channel1 = 0; int channel2 = 1; int channel3 = 2; switch(m_d->acs_config.subTypeParameter) { case KisColorSelectorConfiguration::H: channel1 = 0; break; case KisColorSelectorConfiguration::hsyS: case KisColorSelectorConfiguration::hsiS: case KisColorSelectorConfiguration::hslS: case KisColorSelectorConfiguration::hsvS: channel1 = 1; break; case KisColorSelectorConfiguration::V: case KisColorSelectorConfiguration::L: case KisColorSelectorConfiguration::I: case KisColorSelectorConfiguration::Y: channel1 = 2; break; default: Q_ASSERT_X(false, "", "Invalid acs_config.subTypeParameter"); } switch(m_d->acs_config.mainTypeParameter) { case KisColorSelectorConfiguration::hsySH: modelS = KisVisualColorSelector::HSY; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hsiSH: modelS = KisVisualColorSelector::HSI; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hslSH: modelS = KisVisualColorSelector::HSL; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hsvSH: modelS = KisVisualColorSelector::HSV; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::YH: modelS = KisVisualColorSelector::HSY; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::LH: modelS = KisVisualColorSelector::HSL; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::IH: modelS = KisVisualColorSelector::HSL; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::VH: modelS = KisVisualColorSelector::HSV; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::SY: modelS = KisVisualColorSelector::HSY; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SI: modelS = KisVisualColorSelector::HSI; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SL: modelS = KisVisualColorSelector::HSL; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SV: case KisColorSelectorConfiguration::SV2: modelS = KisVisualColorSelector::HSV; channel2 = 1; channel3 = 2; break; default: Q_ASSERT_X(false, "", "Invalid acs_config.mainTypeParameter"); } if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { modelS = KisVisualColorSelector::HSV; //Triangle only really works in HSV mode. } // L*a*b* mimics the HSX selector types, but model is still Channel (until someone implements LCH) if (m_d->isRGBA) { m_d->model = modelS; m_d->gamma = cfg.readEntry("gamma", 2.2); m_d->applyGamma = (m_d->isLinear && modelS != ColorModel::HSY); // Note: only profiles that define colorants will give precise luma coefficients. // Maybe using the explicitly set values of the Advanced Color Selector is better? QVector luma = m_d->currentCS->lumaCoefficients(); memcpy(m_d->lumaRGB, luma.constData(), 3*sizeof(qreal)); } KisVisualColorSelectorShape *bar; if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, channel1, channel1, m_d->displayRenderer, 20,KisVisualEllipticalSelectorShape::border); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == false) { bar = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, channel1, channel1, m_d->displayRenderer, 20); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == true) { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, channel1, channel1, m_d->displayRenderer, 20, KisVisualEllipticalSelectorShape::borderMirrored); } else { // Accessing bar below would crash since it's not initialized. // Hopefully this can never happen. warnUI << "Invalid subType, cannot initialize KisVisualColorSelectorShape"; Q_ASSERT_X(false, "", "Invalid subType, cannot initialize KisVisualColorSelectorShape"); return; } m_d->widgetlist.append(bar); KisVisualColorSelectorShape *block; if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { block = new KisVisualTriangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional, m_d->currentCS, channel2, channel3, m_d->displayRenderer); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) { block = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional, m_d->currentCS, channel2, channel3, m_d->displayRenderer); } else { block = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::twodimensional, m_d->currentCS, channel2, channel3, m_d->displayRenderer); } connect(bar, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); connect(block, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); m_d->widgetlist.append(block); } else if (m_d->currentCS->colorChannelCount() == 4) { KisVisualRectangleSelectorShape *block = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional, m_d->currentCS, 0, 1); KisVisualRectangleSelectorShape *block2 = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional, m_d->currentCS, 2, 3); connect(block, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); connect(block2, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); m_d->widgetlist.append(block); m_d->widgetlist.append(block2); } this->setLayout(layout); // make sure we call "our" resize function KisVisualColorSelector::resizeEvent(0); // finally recalculate channel values and update widgets if (m_d->displayRenderer) { slotDisplayConfigurationChanged(); } m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor); Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setChannelValues(m_d->channelValues, true); shape->setAcceptTabletEvents(m_d->acceptTabletEvents); // if this widget is currently visible, new children are hidden by default shape->show(); } if (m_d->model != oldModel) { emit sigColorModelChanged(); } } -void KisVisualColorSelector::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) { +void KisVisualColorSelector::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) +{ + if (m_d->displayRenderer) { + m_d->displayRenderer->disconnect(this); + } m_d->displayRenderer = displayRenderer; if (m_d->widgetlist.size()>0) { Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setDisplayRenderer(displayRenderer); } } connect(m_d->displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(slotDisplayConfigurationChanged()), Qt::UniqueConnection); slotDisplayConfigurationChanged(); } void KisVisualColorSelector::slotCursorMoved(QPointF pos) { const KisVisualColorSelectorShape *shape = qobject_cast(sender()); Q_ASSERT(shape); QVector channels = shape->getChannels(); m_d->channelValues[channels.at(0)] = pos.x(); if (shape->getDimensions() == KisVisualColorSelectorShape::twodimensional) { m_d->channelValues[channels.at(1)] = pos.y(); } KoColor newColor = convertShapeCoordsToKoColor(m_d->channelValues); if (newColor != m_d->currentcolor) { m_d->currentcolor = newColor; emit sigNewColor(m_d->currentcolor); } if (isHSXModel()) { emit sigHSXChanged(QVector3D(m_d->channelValues)); } Q_FOREACH (KisVisualColorSelectorShape *widget, m_d->widgetlist) { if (widget != shape){ widget->setChannelValues(m_d->channelValues, false); } } } void KisVisualColorSelector::resizeEvent(QResizeEvent *) { int sizeValue = qMin(width(), height()); int borderWidth = qMax(sizeValue*0.1, 20.0); QRect newrect(0,0, this->geometry().width(), this->geometry().height()); if (!m_d->currentCS) { slotSetColorSpace(m_d->currentcolor.colorSpace()); } if (m_d->currentCS->colorChannelCount()==3) { // set border width first, else the resized painting may have happened already, and we'd have to re-render m_d->widgetlist.at(0)->setBorderWidth(borderWidth); if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) { m_d->widgetlist.at(0)->resize(sizeValue,sizeValue); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==false) { m_d->widgetlist.at(0)->resize(borderWidth, sizeValue); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==true) { m_d->widgetlist.at(0)->resize(sizeValue,sizeValue); } if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForTriangle(newrect)); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForSquare(newrect)); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Wheel) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForCircle(newrect)); } } else if (m_d->currentCS->colorChannelCount() == 4) { int sizeBlock = qMin(width()/2 - 8, height()); m_d->widgetlist.at(0)->setGeometry(0, 0, sizeBlock, sizeBlock); m_d->widgetlist.at(1)->setGeometry(sizeBlock + 8, 0, sizeBlock, sizeBlock); } } diff --git a/libs/widgets/KisVisualColorSelector.h b/libs/widgets/KisVisualColorSelector.h index ceae1aa13f..a488e4ef5f 100644 --- a/libs/widgets/KisVisualColorSelector.h +++ b/libs/widgets/KisVisualColorSelector.h @@ -1,103 +1,101 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_VISUAL_COLOR_SELECTOR_H #define KIS_VISUAL_COLOR_SELECTOR_H #include #include -#include -#include -#include #include -#include -#include "KoColorDisplayRendererInterface.h" #include "KisColorSelectorConfiguration.h" #include "KisColorSelectorInterface.h" #include "kritawidgets_export.h" +class KoColorSpace; +class KoColorDisplayRendererInterface; + /** * @brief The KisVisualColorSelector class * * This gives a color selector box that draws gradients and everything. * * Unlike other color selectors, this one draws the full gamut of the given * colorspace. */ class KRITAWIDGETS_EXPORT KisVisualColorSelector : public KisColorSelectorInterface { Q_OBJECT public: enum ColorModel { None, Channel, HSV, HSL, HSI, HSY, YUV }; explicit KisVisualColorSelector(QWidget *parent = 0); ~KisVisualColorSelector() override; /** * @brief setConfig * @param forceCircular * Force circular is for space where you only have room for a circular selector. * @param forceSelfUpdate * ignored, can possibly be removed from parent class now */ void setConfig(bool forceCircular, bool forceSelfUpdate) override; void setAcceptTabletEvents(bool on); KoColor getCurrentColor() const override; QVector4D getChannelValues() const; ColorModel getColorModel() const; bool isHSXModel() const; KoColor convertShapeCoordsToKoColor(const QVector4D &coordinates) const; QVector4D convertKoColorToShapeCoordinates(KoColor c) const; public Q_SLOTS: void slotSetColor(const KoColor &c) override; void slotSetColorSpace(const KoColorSpace *cs) override; void slotSetHSX(const QVector3D &hsx); void configurationChanged(); void setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) override; private Q_SLOTS: void slotCursorMoved(QPointF pos); void slotDisplayConfigurationChanged(); void slotRebuildSelectors(); Q_SIGNALS: /** * @brief sigColorModelChanged is emitted whenever the selector's color model changes. * * This is mostly relevant for configuration changes where the same RGB model * gets represented in a different way like HSV, HSL etc. so the values of * sigHSXChanged() change meaning. * * @see getColorModel() */ void sigColorModelChanged(); void sigHSXChanged(const QVector3D &hsx); protected: void resizeEvent(QResizeEvent *) override; private: struct Private; const QScopedPointer m_d; }; #endif diff --git a/libs/widgets/KisVisualColorSelectorShape.cpp b/libs/widgets/KisVisualColorSelectorShape.cpp index 6417fb1fc7..8d4b66fcc0 100644 --- a/libs/widgets/KisVisualColorSelectorShape.cpp +++ b/libs/widgets/KisVisualColorSelectorShape.cpp @@ -1,388 +1,382 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisVisualColorSelectorShape.h" #include -#include +#include #include -#include #include #include #include #include #include -#include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoChannelInfo.h" #include #include -#include "kis_signal_compressor.h" #include "kis_debug.h" struct KisVisualColorSelectorShape::Private { QImage gradient; QImage alphaMask; QImage fullSelector; bool imagesNeedUpdate { true }; bool alphaNeedsUpdate { true }; bool acceptTabletEvents { false }; QPointF currentCoordinates; // somewhat redundant? QPointF dragStart; QVector4D currentChannelValues; Dimensions dimension; const KoColorSpace *colorSpace; int channel1; int channel2; const KoColorDisplayRendererInterface *displayRenderer = 0; }; KisVisualColorSelectorShape::KisVisualColorSelectorShape(QWidget *parent, KisVisualColorSelectorShape::Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer): QWidget(parent), m_d(new Private) { m_d->dimension = dimension; m_d->colorSpace = cs; int maxchannel = m_d->colorSpace->colorChannelCount()-1; m_d->channel1 = qBound(0, channel1, maxchannel); m_d->channel2 = qBound(0, channel2, maxchannel); this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setDisplayRenderer(displayRenderer); } KisVisualColorSelectorShape::~KisVisualColorSelectorShape() { } QPointF KisVisualColorSelectorShape::getCursorPosition() { return m_d->currentCoordinates; } void KisVisualColorSelectorShape::setCursorPosition(QPointF position, bool signal) { QPointF newPos(qBound(0.0, position.x(), 1.0), qBound(0.0, position.y(), 1.0)); if (newPos != m_d->currentCoordinates) { m_d->currentCoordinates = newPos; // for internal consistency, because we have a bit of redundancy here m_d->currentChannelValues[m_d->channel1] = newPos.x(); if (m_d->dimension == Dimensions::twodimensional){ m_d->currentChannelValues[m_d->channel2] = newPos.y(); } update(); if (signal){ emit sigCursorMoved(newPos); } } } void KisVisualColorSelectorShape::setChannelValues(QVector4D channelValues, bool setCursor) { //qDebug() << this << "setChannelValues"; m_d->currentChannelValues = channelValues; if (setCursor) { m_d->currentCoordinates = QPointF(qBound(0.f, channelValues[m_d->channel1], 1.f), qBound(0.f, channelValues[m_d->channel2], 1.f)); } else { // for internal consistency, because we have a bit of redundancy here m_d->currentChannelValues[m_d->channel1] = m_d->currentCoordinates.x(); if (m_d->dimension == Dimensions::twodimensional){ m_d->currentChannelValues[m_d->channel2] = m_d->currentCoordinates.y(); } } m_d->imagesNeedUpdate = true; update(); } void KisVisualColorSelectorShape::setAcceptTabletEvents(bool on) { m_d->acceptTabletEvents = on; } void KisVisualColorSelectorShape::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { - if (m_d->displayRenderer) { - m_d->displayRenderer->disconnect(this); - } m_d->displayRenderer = displayRenderer; } else { m_d->displayRenderer = KoDumbColorDisplayRenderer::instance(); } } void KisVisualColorSelectorShape::forceImageUpdate() { //qDebug() << this << "forceImageUpdate"; m_d->alphaNeedsUpdate = true; m_d->imagesNeedUpdate = true; } QColor KisVisualColorSelectorShape::getColorFromConverter(KoColor c){ QColor col; KoColor color = c; if (m_d->displayRenderer) { color.convertTo(m_d->displayRenderer->getPaintingColorSpace()); col = m_d->displayRenderer->toQColor(c); } else { col = c.toQColor(); } return col; } // currently unused? void KisVisualColorSelectorShape::slotSetActiveChannels(int channel1, int channel2) { //qDebug() << this << "slotSetActiveChannels"; int maxchannel = m_d->colorSpace->colorChannelCount()-1; m_d->channel1 = qBound(0, channel1, maxchannel); m_d->channel2 = qBound(0, channel2, maxchannel); m_d->imagesNeedUpdate = true; update(); } bool KisVisualColorSelectorShape::imagesNeedUpdate() const { return m_d->imagesNeedUpdate; } QImage KisVisualColorSelectorShape::getImageMap() { //qDebug() << this << ">>>>>>>>> getImageMap()" << m_d->imagesNeedUpdate; if (m_d->imagesNeedUpdate) { // Fill a buffer with the right kocolors m_d->gradient = renderBackground(m_d->currentChannelValues, m_d->colorSpace->pixelSize()); m_d->imagesNeedUpdate = false; } return m_d->gradient; } const QImage KisVisualColorSelectorShape::getAlphaMask() const { if (m_d->alphaNeedsUpdate) { m_d->alphaMask = renderAlphaMask(); m_d->alphaNeedsUpdate = false; } return m_d->alphaMask; } QImage KisVisualColorSelectorShape::convertImageMap(const quint8 *rawColor, quint32 bufferSize, QSize imgSize) const { Q_ASSERT(bufferSize == imgSize.width() * imgSize.height() * m_d->colorSpace->pixelSize()); QImage image; // Convert the buffer to a qimage if (m_d->displayRenderer) { image = m_d->displayRenderer->convertToQImage(m_d->colorSpace, rawColor, imgSize.width(), imgSize.height()); } else { image = m_d->colorSpace->convertToQImage(rawColor, imgSize.width(), imgSize.height(), 0, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // safeguard: if (image.isNull()) { image = QImage(width(), height(), QImage::Format_ARGB32); image.fill(Qt::black); } return image; } QImage KisVisualColorSelectorShape::renderBackground(const QVector4D &channelValues, quint32 pixelSize) const { const KisVisualColorSelector *selector = qobject_cast(parent()); Q_ASSERT(selector); // Hi-DPI aware rendering requires that we determine the device pixel dimension; // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller... const qreal deviceDivider = 1.0 / devicePixelRatioF(); const int deviceWidth = qCeil(width() * devicePixelRatioF()); const int deviceHeight = qCeil(height() * devicePixelRatioF()); quint32 imageSize = deviceWidth * deviceHeight * m_d->colorSpace->pixelSize(); QScopedArrayPointer raw(new quint8[imageSize] {}); quint8 *dataPtr = raw.data(); QVector4D coordinates = channelValues; QImage alpha = getAlphaMask(); bool checkAlpha = !alpha.isNull() && alpha.valid(deviceWidth - 1, deviceHeight - 1); KIS_SAFE_ASSERT_RECOVER(!checkAlpha || alpha.format() == QImage::Format_Alpha8) { checkAlpha = false; } KoColor filler(Qt::white, m_d->colorSpace); for (int y = 0; y < deviceHeight; y++) { const uchar *alphaLine = checkAlpha ? alpha.scanLine(y) : 0; for (int x=0; x < deviceWidth; x++) { if (!checkAlpha || alphaLine[x]) { QPointF newcoordinate = convertWidgetCoordinateToShapeCoordinate(QPointF(x, y) * deviceDivider); coordinates[m_d->channel1] = newcoordinate.x(); if (m_d->dimension == Dimensions::twodimensional) { coordinates[m_d->channel2] = newcoordinate.y(); } KoColor c = selector->convertShapeCoordsToKoColor(coordinates); memcpy(dataPtr, c.data(), pixelSize); } else { // need to write a color with non-zero alpha, otherwise the display converter // will for some arcane reason crop the final QImage and screw rendering memcpy(dataPtr, filler.data(), pixelSize); } dataPtr += pixelSize; } } QImage image = convertImageMap(raw.data(), imageSize, QSize(deviceWidth, deviceHeight)); image.setDevicePixelRatio(devicePixelRatioF()); if (!alpha.isNull()) { QPainter painter(&image); // transfer alphaMask to Alpha channel painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); painter.drawImage(0, 0, alpha); } return image; } QImage KisVisualColorSelectorShape::renderAlphaMask() const { return QImage(); } QPointF KisVisualColorSelectorShape::mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const { Q_UNUSED(dragStart) return convertWidgetCoordinateToShapeCoordinate(pos); } void KisVisualColorSelectorShape::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { m_d->dragStart = e->localPos(); QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart); setCursorPosition(coordinates, true); } else { e->ignore(); } } void KisVisualColorSelectorShape::mouseMoveEvent(QMouseEvent *e) { if (e->buttons() & Qt::LeftButton) { QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart); setCursorPosition(coordinates, true); } else { e->ignore(); } } void KisVisualColorSelectorShape::mouseReleaseEvent(QMouseEvent *e) { if (e->button() != Qt::LeftButton) { e->ignore(); } } void KisVisualColorSelectorShape::tabletEvent(QTabletEvent* event) { // only accept tablet events that are associated to "left" button // NOTE: QTabletEvent does not have a windowPos() equivalent, but we don't need it if (m_d->acceptTabletEvents && (event->button() == Qt::LeftButton || (event->buttons() & Qt::LeftButton))) { event->accept(); switch (event->type()) { case QEvent::TabletPress: { QMouseEvent mouseEvent(QEvent::MouseButtonPress, event->posF(), event->posF(), event->globalPosF(), event->button(), event->buttons(), event->modifiers(), Qt::MouseEventSynthesizedByApplication); mousePressEvent(&mouseEvent); break; } case QEvent::TabletMove: { QMouseEvent mouseEvent(QEvent::MouseMove, event->posF(), event->posF(), event->globalPosF(), event->button(), event->buttons(), event->modifiers(), Qt::MouseEventSynthesizedByApplication); mouseMoveEvent(&mouseEvent); break; } case QEvent::TabletRelease: { QMouseEvent mouseEvent(QEvent::MouseButtonRelease, event->posF(), event->posF(), event->globalPosF(), event->button(), event->buttons(), event->modifiers(), Qt::MouseEventSynthesizedByApplication); mouseReleaseEvent(&mouseEvent); break; } default: event->ignore(); } } } void KisVisualColorSelectorShape::paintEvent(QPaintEvent*) { QPainter painter(this); drawCursor(); painter.drawImage(0,0,m_d->fullSelector); } void KisVisualColorSelectorShape::resizeEvent(QResizeEvent *) { forceImageUpdate(); setMask(getMaskMap()); } KisVisualColorSelectorShape::Dimensions KisVisualColorSelectorShape::getDimensions() const { return m_d->dimension; } void KisVisualColorSelectorShape::setFullImage(QImage full) { m_d->fullSelector = full; } KoColor KisVisualColorSelectorShape::getCurrentColor() { const KisVisualColorSelector *selector = qobject_cast(parent()); if (selector) { return selector->convertShapeCoordsToKoColor(m_d->currentChannelValues); } return KoColor(m_d->colorSpace); } QVector KisVisualColorSelectorShape::getChannels() const { QVector channels(2); channels[0] = m_d->channel1; channels[1] = m_d->channel2; return channels; } diff --git a/libs/widgets/KisVisualEllipticalSelectorShape.cpp b/libs/widgets/KisVisualEllipticalSelectorShape.cpp index abbfa8275b..d4d10163f3 100644 --- a/libs/widgets/KisVisualEllipticalSelectorShape.cpp +++ b/libs/widgets/KisVisualEllipticalSelectorShape.cpp @@ -1,257 +1,242 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisVisualEllipticalSelectorShape.h" #include -#include #include -#include #include -#include -#include -#include -#include -#include +#include #include -#include -#include - -#include "KoColorConversions.h" -#include "KoColorDisplayRendererInterface.h" -#include "KoChannelInfo.h" -#include -#include -#include "kis_signal_compressor.h" #include "kis_debug.h" #include "kis_global.h" KisVisualEllipticalSelectorShape::KisVisualEllipticalSelectorShape(QWidget *parent, Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer, int barWidth, singelDTypes d) : KisVisualColorSelectorShape(parent, dimension, cs, channel1, channel2, displayRenderer) { //qDebug() << "creating KisVisualEllipticalSelectorShape" << this; m_type = d; m_barWidth = barWidth; } KisVisualEllipticalSelectorShape::~KisVisualEllipticalSelectorShape() { //qDebug() << "deleting KisVisualEllipticalSelectorShape" << this; } QSize KisVisualEllipticalSelectorShape::sizeHint() const { return QSize(180,180); } void KisVisualEllipticalSelectorShape::setBorderWidth(int width) { m_barWidth = width; forceImageUpdate(); update(); } QRect KisVisualEllipticalSelectorShape::getSpaceForSquare(QRect geom) { int sizeValue = qMin(width(),height()); QRect b(geom.left(), geom.top(), sizeValue, sizeValue); QLineF radius(b.center(), QPointF(b.left()+m_barWidth, b.center().y()) ); radius.setAngle(135); QPointF tl = radius.p2(); radius.setAngle(315); QPointF br = radius.p2(); QRect r(tl.toPoint(), br.toPoint()); return r; } QRect KisVisualEllipticalSelectorShape::getSpaceForCircle(QRect geom) { int sizeValue = qMin(width(),height()); QRect b(geom.left(), geom.top(), sizeValue, sizeValue); QPointF tl = QPointF (b.topLeft().x()+m_barWidth, b.topLeft().y()+m_barWidth); QPointF br = QPointF (b.bottomRight().x()-m_barWidth, b.bottomRight().y()-m_barWidth); QRect r(tl.toPoint(), br.toPoint()); return r; } QRect KisVisualEllipticalSelectorShape::getSpaceForTriangle(QRect geom) { int sizeValue = qMin(width(),height()); QPointF center(0.5 * width(), 0.5 * height()); qreal radius = 0.5 * sizeValue - (m_barWidth + 4); QLineF rLine(center, QPointF(center.x() + radius, center.y())); rLine.setAngle(330); QPoint br(rLine.p2().toPoint()); //QPoint br(qCeil(rLine.p2().x()), qCeil(rLine.p2().y())); QPoint tl(width() - br.x(), m_barWidth + 4); QRect bound(tl, br); // adjust with triangle default margin for cursor rendering // it's not +5 because above calculation is for pixel center and ignores // the fact that dimensions are then effectively 1px smaller... bound.adjust(-5, -5, 4, 4); return bound.intersected(geom); } QPointF KisVisualEllipticalSelectorShape::convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const { qreal offset = 7.0; qreal a = (qreal)width()*0.5; QPointF center(a, a); QLineF line(center, QPoint((m_barWidth*0.5),a)); qreal angle = coordinate.x()*360.0; angle = 360.0 - fmod(angle+180.0, 360.0); if (m_type==KisVisualEllipticalSelectorShape::borderMirrored) { angle = (coordinate.x()/2)*360.0; angle = fmod((angle+270.0), 360.0); } line.setAngle(angle); if (getDimensions()!=KisVisualColorSelectorShape::onedimensional) { line.setLength(qMin(coordinate.y()*(a-offset), a-offset)); } return line.p2(); } QPointF KisVisualEllipticalSelectorShape::convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const { //default implementation: qreal x = 0.5; qreal y = 1.0; qreal offset = 7.0; QPointF center = QRectF(QPointF(0.0, 0.0), this->size()).center(); qreal a = (qreal(this->width()) / qreal(2)); qreal xRel = center.x()-coordinate.x(); qreal yRel = center.y()-coordinate.y(); qreal radius = sqrt(xRel*xRel+yRel*yRel); if (m_type!=KisVisualEllipticalSelectorShape::borderMirrored){ qreal angle = atan2(yRel, xRel); angle = kisRadiansToDegrees(angle); angle = fmod(angle+360, 360.0); x = angle/360.0; if (getDimensions()==KisVisualColorSelectorShape::twodimensional) { y = qBound(0.0,radius/(a-offset), 1.0); } } else { qreal angle = atan2(xRel, yRel); angle = kisRadiansToDegrees(angle); angle = fmod(angle+180, 360.0); if (angle>180.0) { angle = 360.0-angle; } x = (angle/360.0)*2; if (getDimensions()==KisVisualColorSelectorShape::twodimensional) { y = qBound(0.0,(radius+offset)/a, 1.0); } } return QPointF(x, y); } QPointF KisVisualEllipticalSelectorShape::mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const { QPointF pos2(pos); if (m_type == KisVisualEllipticalSelectorShape::borderMirrored) { qreal h_center = width()/2.0; bool start_left = dragStart.x() < h_center; bool cursor_left = pos.x() < h_center; if (start_left != cursor_left) { pos2.setX(h_center); } } return convertWidgetCoordinateToShapeCoordinate(pos2); } QRegion KisVisualEllipticalSelectorShape::getMaskMap() { QRegion mask = QRegion(0,0,width(),height(), QRegion::Ellipse); if (getDimensions()==KisVisualColorSelectorShape::onedimensional) { mask = mask.subtracted(QRegion(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2), QRegion::Ellipse)); } return mask; } QImage KisVisualEllipticalSelectorShape::renderAlphaMask() const { // Hi-DPI aware rendering requires that we determine the device pixel dimension; // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller... const int deviceWidth = qCeil(width() * devicePixelRatioF()); const int deviceHeight = qCeil(height() * devicePixelRatioF()); QImage alphaMask(deviceWidth, deviceHeight, QImage::Format_Alpha8); alphaMask.fill(0); alphaMask.setDevicePixelRatio(devicePixelRatioF()); QPainter painter(&alphaMask); painter.setRenderHint(QPainter::Antialiasing); painter.setBrush(Qt::white); painter.setPen(Qt::NoPen); painter.drawEllipse(2, 2, width() - 4, height() - 4); //painter.setBrush(Qt::black); if (getDimensions() == KisVisualColorSelectorShape::onedimensional) { painter.setCompositionMode(QPainter::CompositionMode_Clear); painter.drawEllipse(m_barWidth - 2, m_barWidth - 2, width() - 2*(m_barWidth-2), height() - 2*(m_barWidth-2)); } return alphaMask; } void KisVisualEllipticalSelectorShape::drawCursor() { //qDebug() << this << "KisVisualEllipticalSelectorShape::drawCursor: image needs update" << imagesNeedUpdate(); QPointF cursorPoint = convertShapeCoordinateToWidgetCoordinate(getCursorPosition()); QImage fullSelector = getImageMap(); QColor col = getColorFromConverter(getCurrentColor()); QPainter painter; painter.begin(&fullSelector); painter.setRenderHint(QPainter::Antialiasing); QBrush fill; fill.setStyle(Qt::SolidPattern); int cursorwidth = 5; if (m_type==KisVisualEllipticalSelectorShape::borderMirrored) { painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); QPointF mirror(width() - cursorPoint.x(), cursorPoint.y()); painter.drawEllipse(mirror, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1, cursorwidth-1); painter.drawEllipse(mirror, cursorwidth-1, cursorwidth-1); } else { painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1.0, cursorwidth-1.0); } painter.end(); setFullImage(fullSelector); } diff --git a/libs/widgets/KisVisualEllipticalSelectorShape.h b/libs/widgets/KisVisualEllipticalSelectorShape.h index a484468096..8e9008fe5b 100644 --- a/libs/widgets/KisVisualEllipticalSelectorShape.h +++ b/libs/widgets/KisVisualEllipticalSelectorShape.h @@ -1,73 +1,62 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISVISUALCOLORSELECTOR_H #define KISVISUALCOLORSELECTOR_H -#include -#include -#include -#include -#include - -#include -#include -#include "KoColorDisplayRendererInterface.h" - -#include "KisColorSelectorConfiguration.h" #include "KisVisualColorSelectorShape.h" class KisVisualEllipticalSelectorShape : public KisVisualColorSelectorShape { Q_OBJECT public: enum singelDTypes{border, borderMirrored}; explicit KisVisualEllipticalSelectorShape(QWidget *parent, Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance(), int barWidth=20, KisVisualEllipticalSelectorShape::singelDTypes d = KisVisualEllipticalSelectorShape::border ); ~KisVisualEllipticalSelectorShape() override; void setBorderWidth(int width) override; /** * @brief getSpaceForSquare * @param geom the full widget rectangle * @return rectangle with enough space for second widget */ QRect getSpaceForSquare(QRect geom) override; QRect getSpaceForCircle(QRect geom) override; QRect getSpaceForTriangle(QRect geom) override; protected: QImage renderAlphaMask() const override; QPointF mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const override; private: QPointF convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const override; QPointF convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const override; singelDTypes m_type; int m_barWidth; QRegion getMaskMap() override; void drawCursor() override; QSize sizeHint() const override; }; #endif // KISVISUALCOLORSELECTOR_H diff --git a/libs/widgets/KisVisualRectangleSelectorShape.cpp b/libs/widgets/KisVisualRectangleSelectorShape.cpp index 58e7e7a4be..477ad6f0ae 100644 --- a/libs/widgets/KisVisualRectangleSelectorShape.cpp +++ b/libs/widgets/KisVisualRectangleSelectorShape.cpp @@ -1,376 +1,363 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisVisualRectangleSelectorShape.h" #include -#include #include -#include #include -#include -#include #include -#include +#include #include -#include -#include - -#include "KoColorConversions.h" -#include "KoColorDisplayRendererInterface.h" -#include "KoChannelInfo.h" -#include -#include -#include "kis_signal_compressor.h" #include "kis_debug.h" KisVisualRectangleSelectorShape::KisVisualRectangleSelectorShape(QWidget *parent, Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer, int width, singelDTypes d) : KisVisualColorSelectorShape(parent, dimension, cs, channel1, channel2, displayRenderer) { //qDebug() << "creating KisVisualRectangleSelectorShape" << this; m_type = d; m_barWidth = width; } KisVisualRectangleSelectorShape::~KisVisualRectangleSelectorShape() { //qDebug() << "deleting KisVisualRectangleSelectorShape" << this; } void KisVisualRectangleSelectorShape::setBorderWidth(int width) { m_barWidth = width; } QRect KisVisualRectangleSelectorShape::getSpaceForSquare(QRect geom) { QPointF tl; QPointF br; if (m_type==KisVisualRectangleSelectorShape::vertical) { br = geom.bottomRight(); tl = QPoint(geom.topLeft().x()+m_barWidth, geom.topLeft().y()); } else if (m_type==KisVisualRectangleSelectorShape::horizontal) { br = geom.bottomRight(); tl = QPoint(geom.topLeft().x(), geom.topLeft().y()+m_barWidth); } else { tl = QPointF (geom.topLeft().x()+m_barWidth, geom.topLeft().y()+m_barWidth); br = QPointF (geom.bottomRight().x()-m_barWidth, geom.bottomRight().y()-m_barWidth); } QRect a(tl.toPoint(), br.toPoint()); QRect r(a.left(), a.top(), qMin(a.height(), a.width()), qMin(a.height(), a.width())); return r; } QRect KisVisualRectangleSelectorShape::getSpaceForCircle(QRect geom) { return getSpaceForSquare(geom); } QRect KisVisualRectangleSelectorShape::getSpaceForTriangle(QRect geom) { return getSpaceForSquare(geom); } QPointF KisVisualRectangleSelectorShape::convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const { // Reminder: in Qt widget space, origin is top-left, but we want zero y to be at the bottom qreal x = 0.5 * m_barWidth; qreal y = 0.5 * m_barWidth; qreal offset = 5.0; KisVisualColorSelectorShape::Dimensions dimension = getDimensions(); if (dimension == KisVisualColorSelectorShape::onedimensional) { if ( m_type == KisVisualRectangleSelectorShape::vertical) { y = qMin((1.0 - coordinate.x())*(height()-offset*2)+offset, (qreal)height()); } else if (m_type == KisVisualRectangleSelectorShape::horizontal) { x = qMin(coordinate.x()*(width()-offset*2)+offset, (qreal)width()); } else if (m_type == KisVisualRectangleSelectorShape::border) { QRectF innerRect(m_barWidth/2, m_barWidth/2, width()-m_barWidth, height()-m_barWidth); QPointF left (innerRect.left(),innerRect.center().y()); QList polygonLines; polygonLines.append(QLineF(left, innerRect.topLeft())); polygonLines.append(QLineF(innerRect.topLeft(), innerRect.topRight())); polygonLines.append(QLineF(innerRect.topRight(), innerRect.bottomRight())); polygonLines.append(QLineF(innerRect.bottomRight(), innerRect.bottomLeft())); polygonLines.append(QLineF(innerRect.bottomLeft(), left)); qreal totalLength =0.0; Q_FOREACH(QLineF line, polygonLines) { totalLength += line.length(); } qreal length = coordinate.x()*totalLength; QPointF intersect(x,y); Q_FOREACH(QLineF line, polygonLines) { if (line.length()>length && length>0){ intersect = line.pointAt(length/line.length()); } length-=line.length(); } x = qRound(intersect.x()); y = qRound(intersect.y()); } else /*if (m_type == KisVisualRectangleSelectorShape::borderMirrored)*/ { QRectF innerRect(m_barWidth/2, m_barWidth/2, width()-m_barWidth, height()-m_barWidth); QPointF bottom (innerRect.center().x(), innerRect.bottom()); QList polygonLines; polygonLines.append(QLineF(bottom, innerRect.bottomLeft())); polygonLines.append(QLineF(innerRect.bottomLeft(), innerRect.topLeft())); polygonLines.append(QLineF(innerRect.topLeft(), innerRect.topRight())); polygonLines.append(QLineF(innerRect.topRight(), innerRect.bottomRight())); polygonLines.append(QLineF(innerRect.bottomRight(), bottom)); qreal totalLength =0.0; Q_FOREACH(QLineF line, polygonLines) { totalLength += line.length(); } qreal length = coordinate.x()*(totalLength/2); QPointF intersect(x,y); if (coordinate.y()==1) { for (int i = polygonLines.size()-1; i==0; i--) { QLineF line = polygonLines.at(i); if (line.length()>length && length>0){ intersect = line.pointAt(length/line.length()); } length-=line.length(); } } else { Q_FOREACH(QLineF line, polygonLines) { if (line.length()>length && length>0){ intersect = line.pointAt(length/line.length()); } length-=line.length(); } } x = qRound(intersect.x()); y = qRound(intersect.y()); } } else { x = qMin(coordinate.x()*(width()-offset*2)+offset, (qreal)width()); y = qMin((1.0 - coordinate.y())*(height()-offset*2)+offset, (qreal)height()); } return QPointF(x,y); } QPointF KisVisualRectangleSelectorShape::convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const { // Reminder: in Qt widget space, origin is top-left, but we want zero y to be at the bottom //default values: qreal x = 0.5; qreal y = 0.5; qreal offset = 5.0; KisVisualColorSelectorShape::Dimensions dimension = getDimensions(); if (dimension == KisVisualColorSelectorShape::onedimensional ) { if (m_type == KisVisualRectangleSelectorShape::vertical) { x = 1.0 - (coordinate.y()-offset)/(height()-offset*2); } else if (m_type == KisVisualRectangleSelectorShape::horizontal) { x = (coordinate.x()-offset)/(width()-offset*2); } else if (m_type == KisVisualRectangleSelectorShape::border) { //border QRectF innerRect(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2)); QPointF left (innerRect.left(),innerRect.center().y()); QList polygonLines; polygonLines.append(QLineF(left, innerRect.topLeft())); polygonLines.append(QLineF(innerRect.topLeft(), innerRect.topRight())); polygonLines.append(QLineF(innerRect.topRight(), innerRect.bottomRight())); polygonLines.append(QLineF(innerRect.bottomRight(), innerRect.bottomLeft())); polygonLines.append(QLineF(innerRect.bottomLeft(), left)); QLineF radius(coordinate, this->geometry().center()); QPointF intersect(0.5,0.5); qreal length = 0.0; qreal totalLength = 0.0; bool foundIntersect = false; Q_FOREACH(QLineF line, polygonLines) { if (line.intersect(radius,&intersect)==QLineF::BoundedIntersection && foundIntersect==false) { foundIntersect = true; length+=QLineF(line.p1(), intersect).length(); } if (foundIntersect==false) { length+=line.length(); } totalLength+=line.length(); } x = length/totalLength; } else /*if (m_type == KisVisualRectangleSelectorShape::borderMirrored)*/ { //border QRectF innerRect(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2)); QPointF bottom (innerRect.center().x(), innerRect.bottom()); QList polygonLines; polygonLines.append(QLineF(bottom, innerRect.bottomLeft())); polygonLines.append(QLineF(innerRect.bottomLeft(), innerRect.topLeft())); polygonLines.append(QLineF(innerRect.topLeft(), innerRect.topRight())); polygonLines.append(QLineF(innerRect.topRight(), innerRect.bottomRight())); polygonLines.append(QLineF(innerRect.bottomRight(), bottom)); QLineF radius(coordinate, this->geometry().center()); QPointF intersect(0.5,0.5); qreal length = 0.0; qreal totalLength = 0.0; bool foundIntersect = false; Q_FOREACH(QLineF line, polygonLines) { if (line.intersect(radius,&intersect)==QLineF::BoundedIntersection && foundIntersect==false) { foundIntersect = true; length+=QLineF(line.p1(), intersect).length(); } if (foundIntersect==false) { length+=line.length(); } totalLength+=line.length(); } int halflength = totalLength/2; if (length>halflength) { x = (halflength - (length-halflength))/halflength; y = 1.0; } else { x = length/halflength; y = 0.0; } } } else { x = (coordinate.x()-offset)/(width()-offset*2); y = 1.0 - (coordinate.y()-offset)/(height()-offset*2); } x = qBound((qreal)0.0, x, (qreal)1.0); y = qBound((qreal)0.0, y, (qreal)1.0); return QPointF(x, y); } QRegion KisVisualRectangleSelectorShape::getMaskMap() { QRegion mask = QRegion(0,0,width(),height()); if (m_type==KisVisualRectangleSelectorShape::border || m_type==KisVisualRectangleSelectorShape::borderMirrored) { mask = mask.subtracted(QRegion(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2))); } return mask; } QImage KisVisualRectangleSelectorShape::renderAlphaMask() const { // Hi-DPI aware rendering requires that we determine the device pixel dimension; // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller... const int deviceWidth = qCeil(width() * devicePixelRatioF()); const int deviceHeight = qCeil(height() * devicePixelRatioF()); QImage alphaMask(deviceWidth, deviceHeight, QImage::Format_Alpha8); alphaMask.fill(0); alphaMask.setDevicePixelRatio(devicePixelRatioF()); QPainter painter(&alphaMask); painter.setRenderHint(QPainter::Antialiasing); painter.setBrush(Qt::white); painter.setPen(Qt::NoPen); painter.drawRect(3, 3, width() - 6, height() - 6); if (m_type == border || m_type == borderMirrored) { painter.setBrush(Qt::black); painter.drawRect(m_barWidth, m_barWidth, width() - 2 * m_barWidth, height() - 2 * m_barWidth); } return alphaMask; } void KisVisualRectangleSelectorShape::drawCursor() { //qDebug() << this << "KisVisualRectangleSelectorShape::drawCursor: image needs update" << imagesNeedUpdate(); QPointF cursorPoint = convertShapeCoordinateToWidgetCoordinate(getCursorPosition()); QImage fullSelector = getImageMap(); QColor col = getColorFromConverter(getCurrentColor()); QPainter painter; painter.begin(&fullSelector); painter.setRenderHint(QPainter::Antialiasing); //QPainterPath path; QBrush fill; fill.setStyle(Qt::SolidPattern); int cursorwidth = 5; QRect rect(cursorPoint.toPoint().x()-cursorwidth,cursorPoint.toPoint().y()-cursorwidth, cursorwidth*2,cursorwidth*2); if (m_type==KisVisualRectangleSelectorShape::vertical){ int x = ( cursorPoint.x()-(width()/2)+1 ); int y = ( cursorPoint.y()-cursorwidth ); rect.setCoords(x, y, x+width()-2, y+(cursorwidth*2)); } else { int x = cursorPoint.x()-cursorwidth; int y = cursorPoint.y()-(height()/2)+1; rect.setCoords(x, y, x+(cursorwidth*2), y+cursorwidth-2); } QRectF innerRect(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2)); if (getDimensions() == KisVisualColorSelectorShape::onedimensional && m_type!=KisVisualRectangleSelectorShape::border && m_type!=KisVisualRectangleSelectorShape::borderMirrored) { painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawRect(rect); //set filter conversion! fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); rect.setCoords(rect.topLeft().x()+1, rect.topLeft().y()+1, rect.topLeft().x()+rect.width()-2, rect.topLeft().y()+rect.height()-2); painter.drawRect(rect); }else if(m_type==KisVisualRectangleSelectorShape::borderMirrored){ painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); QPoint mirror(innerRect.center().x()+(innerRect.center().x()-cursorPoint.x()),cursorPoint.y()); painter.drawEllipse(mirror, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1, cursorwidth-1); painter.drawEllipse(mirror, cursorwidth-1, cursorwidth-1); } else { painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1.0, cursorwidth-1.0); } painter.end(); setFullImage(fullSelector); } diff --git a/libs/widgets/KisVisualRectangleSelectorShape.h b/libs/widgets/KisVisualRectangleSelectorShape.h index a0560fe810..dbf4ceff99 100644 --- a/libs/widgets/KisVisualRectangleSelectorShape.h +++ b/libs/widgets/KisVisualRectangleSelectorShape.h @@ -1,71 +1,60 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_VISUAL_RECTANGLE_SELECTOR_SHAPE_H #define KIS_VISUAL_RECTANGLE_SELECTOR_SHAPE_H -#include -#include -#include -#include -#include - -#include -#include -#include "KoColorDisplayRendererInterface.h" - -#include "KisColorSelectorConfiguration.h" #include "KisVisualColorSelectorShape.h" class KisVisualRectangleSelectorShape : public KisVisualColorSelectorShape { Q_OBJECT public: enum singelDTypes{vertical, horizontal, border, borderMirrored}; explicit KisVisualRectangleSelectorShape(QWidget *parent, Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance(), int width=20, KisVisualRectangleSelectorShape::singelDTypes d = KisVisualRectangleSelectorShape::vertical ); ~KisVisualRectangleSelectorShape() override; void setBorderWidth(int width) override; /** * @brief getSpaceForSquare * @param geom the full widget rectangle * @return rectangle with enough space for second widget */ QRect getSpaceForSquare(QRect geom) override; QRect getSpaceForCircle(QRect geom) override; QRect getSpaceForTriangle(QRect geom) override; protected: QImage renderAlphaMask() const override; private: QPointF convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const override; QPointF convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const override; singelDTypes m_type; int m_barWidth; QRegion getMaskMap() override; void drawCursor() override; }; #endif diff --git a/libs/widgets/KisVisualTriangleSelectorShape.cpp b/libs/widgets/KisVisualTriangleSelectorShape.cpp index efd2e2ac0f..bb7a3f54c3 100644 --- a/libs/widgets/KisVisualTriangleSelectorShape.cpp +++ b/libs/widgets/KisVisualTriangleSelectorShape.cpp @@ -1,177 +1,162 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisVisualTriangleSelectorShape.h" #include -#include #include -#include #include -#include -#include -#include -#include #include -#include -#include - -#include "KoColorConversions.h" -#include "KoColorDisplayRendererInterface.h" -#include "KoChannelInfo.h" -#include -#include -#include "kis_signal_compressor.h" #include "kis_debug.h" #include "kis_global.h" KisVisualTriangleSelectorShape::KisVisualTriangleSelectorShape(QWidget *parent, Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer, int margin) : KisVisualColorSelectorShape(parent, dimension, cs, channel1, channel2, displayRenderer), m_margin(margin) { //qDebug() << "creating KisVisualTriangleSelectorShape" << this; } KisVisualTriangleSelectorShape::~KisVisualTriangleSelectorShape() { //qDebug() << "deleting KisVisualTriangleSelectorShape" << this; } void KisVisualTriangleSelectorShape::setBorderWidth(int /*width*/) { // triangle doesn't have a 1-dimensional mode } QRect KisVisualTriangleSelectorShape::getSpaceForSquare(QRect geom) { return geom; } QRect KisVisualTriangleSelectorShape::getSpaceForCircle(QRect geom) { return geom; } QRect KisVisualTriangleSelectorShape::getSpaceForTriangle(QRect geom) { return geom; } QPointF KisVisualTriangleSelectorShape::convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const { // margin serves to render the cursor, and triangle is rendered 1px larger than its active area qreal offset = m_margin + 1.0; qreal y = (coordinate.y() * (height() - 1 - 2 * offset)) + offset; qreal triWidth = width() - 1 - 2 * offset; qreal horizontalLineLength = coordinate.y() * triWidth; qreal horizontalLineStart = offset + 0.5 * (triWidth - horizontalLineLength); qreal x = coordinate.x() * horizontalLineLength + horizontalLineStart; return QPointF(x, y); } QPointF KisVisualTriangleSelectorShape::convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const { // margin serves to render the cursor, and triangle is rendered 1px larger than its active area qreal offset = m_margin + 1.0; qreal x = 0.5; qreal y = qBound(0.0, (coordinate.y() - offset)/(height() - 1 - 2 * offset), 1.0); if (y > 0) { qreal triWidth = width() - 1 - 2 * offset; qreal horizontalLineLength = y * triWidth; qreal horizontalLineStart = offset + 0.5 * (triWidth - horizontalLineLength); x = qBound(0.0, (coordinate.x() - horizontalLineStart) / horizontalLineLength, 1.0); } return QPointF(x, y); } QRegion KisVisualTriangleSelectorShape::getMaskMap() { const int cursorWidth = qMax(2 * m_margin, 2); QPolygon maskPoly; maskPoly << QPoint(qFloor(0.5 * (width() - cursorWidth)), 0) << QPoint(qCeil(0.5 * (width() + cursorWidth)), 0) << QPoint(width(), height() - cursorWidth) << QPoint(width(), height()) << QPoint(0, height()) << QPoint(0, height() - cursorWidth); return QRegion(maskPoly); } QImage KisVisualTriangleSelectorShape::renderAlphaMask() const { // Hi-DPI aware rendering requires that we determine the device pixel dimension; // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller... const int deviceWidth = qCeil(width() * devicePixelRatioF()); const int deviceHeight = qCeil(height() * devicePixelRatioF()); QImage alphaMask(deviceWidth, deviceHeight, QImage::Format_Alpha8); alphaMask.fill(0); alphaMask.setDevicePixelRatio(devicePixelRatioF()); QPainter painter(&alphaMask); painter.setRenderHint(QPainter::Antialiasing); painter.setBrush(Qt::white); painter.setPen(Qt::NoPen); QPointF triangle[3] = { QPointF(0.5 * width(), m_margin), QPointF(m_margin, height() - m_margin), QPointF(width() - m_margin, height() - m_margin), }; painter.drawConvexPolygon(triangle, 3); return alphaMask; } void KisVisualTriangleSelectorShape::drawCursor() { //qDebug() << this << "KisVisualTriangleSelectorShape::drawCursor: image needs update" << imagesNeedUpdate(); QPointF cursorPoint = convertShapeCoordinateToWidgetCoordinate(getCursorPosition()); QImage fullSelector = getImageMap(); QColor col = getColorFromConverter(getCurrentColor()); QPainter painter(&fullSelector); painter.setRenderHint(QPainter::Antialiasing); QBrush fill(Qt::SolidPattern); int cursorwidth = 5; painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1.0, cursorwidth-1.0); painter.end(); setFullImage(fullSelector); } diff --git a/libs/widgets/KisVisualTriangleSelectorShape.h b/libs/widgets/KisVisualTriangleSelectorShape.h index b36951491a..26d751c908 100644 --- a/libs/widgets/KisVisualTriangleSelectorShape.h +++ b/libs/widgets/KisVisualTriangleSelectorShape.h @@ -1,71 +1,60 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_VISUAL_TRIANGLE_SELECTOR_SHAPE_H #define KIS_VISUAL_TRIANGLE_SELECTOR_SHAPE_H -#include -#include -#include -#include -#include - -#include -#include -#include "KoColorDisplayRendererInterface.h" - -#include "KisColorSelectorConfiguration.h" #include "KisVisualColorSelectorShape.h" class KisVisualTriangleSelectorShape : public KisVisualColorSelectorShape { Q_OBJECT public: explicit KisVisualTriangleSelectorShape(QWidget *parent, Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance(), int margin = 5 ); ~KisVisualTriangleSelectorShape() override; void setBorderWidth(int /*width*/) override; /** * @brief getSpaceForSquare * @param geom the full widget rectangle * @return rectangle with enough space for second widget */ QRect getSpaceForSquare(QRect geom) override; QRect getSpaceForCircle(QRect geom) override; QRect getSpaceForTriangle(QRect geom) override; protected: QImage renderAlphaMask() const override; private: QPointF convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const override; QPointF convertWidgetCoordinateToShapeCoordinate(QPointF coordinate) const override; QRegion getMaskMap() override; void drawCursor() override; int m_margin { 5 }; }; #endif diff --git a/libs/widgets/KoZoomWidget.cpp b/libs/widgets/KoZoomWidget.cpp index 886e2e9711..4cee49ba88 100644 --- a/libs/widgets/KoZoomWidget.cpp +++ b/libs/widgets/KoZoomWidget.cpp @@ -1,115 +1,115 @@ /* Copyright (C) 2004 Ariya Hidayat Copyright (C) 2006 Peter Simonsson Copyright (C) 2006-2007 C. Boemann Copyright (C) 2014 Sven Langkamp This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoZoomWidget.h" #include #include #include #include #include "KoZoomInput.h" #include "KoIcon.h" class KoZoomWidget::Private { public: Private() : slider(0) , input(0) , aspectButton(0) {} QSlider* slider; KoZoomInput* input; QToolButton* aspectButton; - qreal effectiveZoom; + qreal effectiveZoom {1.0}; }; KoZoomWidget::KoZoomWidget(QWidget* parent, int maxZoom ) : QWidget(parent) , d(new Private) { QHBoxLayout *layout = new QHBoxLayout(this); //layout->setSizeConstraint(QLayout::SetFixedSize); layout->setMargin(0); layout->setSpacing(0); d->input = new KoZoomInput(this); connect(d->input, SIGNAL(zoomLevelChanged(QString)), this, SIGNAL(zoomLevelChanged(QString))); layout->addWidget(d->input); d->slider = new QSlider(Qt::Horizontal); d->slider->setToolTip(i18n("Zoom")); d->slider->setMinimum(0); d->slider->setMaximum(maxZoom); d->slider->setValue(0); d->slider->setSingleStep(1); d->slider->setPageStep(1); d->slider->setMinimumWidth(80); layout->addWidget(d->slider); layout->setStretch(1, 1); d->aspectButton = new QToolButton(this); d->aspectButton->setIcon(koIcon("zoom-pixels")); d->aspectButton->setIconSize(QSize(16,16)); d->aspectButton->setCheckable(true); d->aspectButton->setChecked(true); d->aspectButton->setAutoRaise(true); d->aspectButton->setToolTip(i18n("Use same aspect as pixels")); connect(d->aspectButton, SIGNAL(toggled(bool)), this, SIGNAL(aspectModeChanged(bool))); layout->addWidget(d->aspectButton); connect(d->slider, SIGNAL(valueChanged(int)), this, SIGNAL(sliderValueChanged(int))); } KoZoomWidget::~KoZoomWidget() { } void KoZoomWidget::setZoomLevels(const QStringList &values) { d->input->setZoomLevels(values); } void KoZoomWidget::setCurrentZoomLevel(const QString &valueString) { d->input->setCurrentZoomLevel(valueString); } void KoZoomWidget::setSliderValue(int value) { d->slider->blockSignals(true); d->slider->setValue(value); d->slider->blockSignals(false); } void KoZoomWidget::setAspectMode(bool status) { if(d->aspectButton && d->aspectButton->isChecked() != status) { d->aspectButton->blockSignals(true); d->aspectButton->setChecked(status); d->aspectButton->blockSignals(false); } } diff --git a/plugins/dockers/animation/timeline_docker.cpp b/plugins/dockers/animation/timeline_docker.cpp index e4d9443610..b4f2d81999 100644 --- a/plugins/dockers/animation/timeline_docker.cpp +++ b/plugins/dockers/animation/timeline_docker.cpp @@ -1,606 +1,737 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * Copyright (c) 2020 Emmet O'Neill * Copyright (c) 2020 Eoin O'Neill * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_docker.h" #include #include "QHBoxLayout" #include "QVBoxLayout" #include "QFormLayout" #include "QLabel" #include "QPushButton" #include "QToolButton" #include "QMenu" #include "QWidgetAction" #include "krita_utils.h" #include "kis_canvas2.h" #include "kis_image.h" #include #include "KisViewManager.h" #include "kis_paint_layer.h" #include "KisDocument.h" #include "kis_dummies_facade.h" #include "kis_shape_controller.h" #include "kis_action.h" #include "kis_action_manager.h" #include "kis_animation_player.h" #include "kis_animation_utils.h" #include "kis_image_config.h" #include "kis_keyframe_channel.h" #include "kis_image.h" #include "timeline_frames_model.h" #include "timeline_frames_view.h" #include "kis_time_range.h" #include "kis_animation_frame_cache.h" #include "kis_image_animation_interface.h" #include "kis_signal_auto_connection.h" #include "kis_node_manager.h" #include "kis_transport_controls.h" #include "kis_int_parse_spin_box.h" #include "kis_slider_spin_box.h" #include "kis_signals_blocker.h" TimelineDockerTitleBar::TimelineDockerTitleBar(QWidget* parent) : KisUtilityTitleBar(new QLabel(i18n("Animation Timeline"), parent), parent) { // Transport Controls... transport = new KisTransportControls(this); widgetArea->addWidget(transport); widgetArea->addSpacing(SPACING_UNIT); // Frame Register... frameRegister = new KisIntParseSpinBox(this); frameRegister->setToolTip(i18n("Frame register")); frameRegister->setPrefix("# "); frameRegister->setRange(0, MAX_FRAMES); widgetArea->addWidget(frameRegister); widgetArea->addSpacing(SPACING_UNIT); { // Frame ops... QHBoxLayout *layout = new QHBoxLayout(this); layout->setSpacing(0); layout->setContentsMargins(0,0,0,0); btnAddKeyframe = new QToolButton(this); layout->addWidget(btnAddKeyframe); btnDuplicateKeyframe = new QToolButton(this); layout->addWidget(btnDuplicateKeyframe); btnRemoveKeyframe = new QToolButton(this); layout->addWidget(btnRemoveKeyframe); QWidget *widget = new QWidget(); widget->setLayout(layout); widgetArea->addWidget(widget); } widgetArea->addSpacing(SPACING_UNIT); // Drop Frames.. btnDropFrames = new QToolButton(this); widgetArea->addWidget(btnDropFrames); // Playback Speed.. sbSpeed = new KisSliderSpinBox(this); sbSpeed->setRange(25, 200); sbSpeed->setSingleStep(5); sbSpeed->setValue(100); sbSpeed->setPrefix("Speed: "); sbSpeed->setSuffix(" %"); sbSpeed->setToolTip(i18n("Preview playback speed")); widgetArea->addWidget(sbSpeed); widgetArea->addStretch(); { // Menus.. QHBoxLayout *layout = new QHBoxLayout(this); layout->setSpacing(0); layout->setContentsMargins(SPACING_UNIT,0,0,0); // Onion skins menu. btnOnionSkinsMenu = new QPushButton(KisIconUtils::loadIcon("onion_skin_options"), "", this); btnOnionSkinsMenu->setToolTip(i18n("Onion skins menu")); layout->addWidget(btnOnionSkinsMenu); // Audio menu.. btnAudioMenu = new QPushButton(KisIconUtils::loadIcon("audio-none"), "", this); btnOnionSkinsMenu->setToolTip(i18n("Audio menu")); btnAudioMenu->hide(); // (NOTE: Hidden for now while audio features develop.) layout->addWidget(btnAudioMenu); { // Settings menu.. btnSettingsMenu = new QToolButton(this); btnSettingsMenu->setIcon(KisIconUtils::loadIcon("configure")); btnSettingsMenu->setToolTip(i18n("Animation settings menu")); QWidget *settingsMenuWidget = new QWidget(this); settingsMenuWidget->setLayout(new QHBoxLayout(settingsMenuWidget)); QWidget *fields = new QWidget(settingsMenuWidget); QFormLayout *fieldsLayout = new QFormLayout(settingsMenuWidget); fields->setLayout(fieldsLayout); sbStartFrame = new KisIntParseSpinBox(settingsMenuWidget); sbStartFrame->setMaximum(10000); fieldsLayout->addRow(i18n("Clip Start: "), sbStartFrame); sbEndFrame = new KisIntParseSpinBox(settingsMenuWidget); sbEndFrame->setMaximum(10000); fieldsLayout->addRow(i18n("Clip End: "), sbEndFrame); sbFrameRate = new KisIntParseSpinBox(settingsMenuWidget); sbFrameRate->setMinimum(0); sbFrameRate->setMaximum(180); fieldsLayout->addRow(i18n("Frame Rate: "), sbFrameRate); QWidget *buttons = new QWidget(settingsMenuWidget); buttons->setLayout(new QVBoxLayout(settingsMenuWidget)); btnAutoFrame = new QToolButton(settingsMenuWidget); buttons->layout()->addWidget(btnAutoFrame); buttons->layout()->setAlignment(Qt::AlignTop); settingsMenuWidget->layout()->addWidget(fields); settingsMenuWidget->layout()->addWidget(buttons); layout->addWidget(btnSettingsMenu); QMenu *settingsPopMenu = new QMenu(this); QWidgetAction *settingsMenuAction = new QWidgetAction(this); settingsMenuAction->setDefaultWidget(settingsMenuWidget); settingsPopMenu->addAction(settingsMenuAction); btnSettingsMenu->setPopupMode(QToolButton::InstantPopup); btnSettingsMenu->setMenu(settingsPopMenu); } QWidget *widget = new QWidget(); widget->setLayout(layout); widgetArea->addWidget(widget); } } struct TimelineDocker::Private { Private(QWidget *parent) : framesModel(new TimelineFramesModel(parent)), framesView(new TimelineFramesView(parent)), titlebar(new TimelineDockerTitleBar(parent)), mainWindow(nullptr) { framesView->setModel(framesModel); } TimelineFramesModel *framesModel; TimelineFramesView *framesView; TimelineDockerTitleBar *titlebar; QPointer canvas; KisSignalAutoConnectionsStore canvasConnections; KisMainWindow *mainWindow; }; TimelineDocker::TimelineDocker() : QDockWidget(i18n("Animation Timeline")) , m_d(new Private(this)) { setWidget(m_d->framesView); setTitleBarWidget(m_d->titlebar); connect(m_d->titlebar->transport, SIGNAL(back()), this, SLOT(previousFrame())); connect(m_d->titlebar->transport, SIGNAL(stop()), this, SLOT(stop())); connect(m_d->titlebar->transport, SIGNAL(playPause()), this, SLOT(playPause())); connect(m_d->titlebar->transport, SIGNAL(forward()), this, SLOT(nextFrame())); connect(m_d->titlebar->frameRegister, SIGNAL(valueChanged(int)), SLOT(goToFrame(int))); connect(m_d->titlebar->sbStartFrame, SIGNAL(valueChanged(int)), SLOT(setStartFrame(int))); connect(m_d->titlebar->sbEndFrame, SIGNAL(valueChanged(int)), SLOT(setEndFrame(int))); connect(m_d->titlebar->sbFrameRate, SIGNAL(valueChanged(int)), SLOT(setFrameRate(int))); connect(m_d->titlebar->sbSpeed, SIGNAL(valueChanged(int)), SLOT(setPlaybackSpeed(int))); connect(m_d->titlebar->btnOnionSkinsMenu, &QPushButton::released, [this](){ if (m_d->mainWindow) { QDockWidget *docker = m_d->mainWindow->dockWidget("OnionSkinsDocker"); if (docker) { docker->setVisible(!docker->isVisible()); } } }); } TimelineDocker::~TimelineDocker() { } struct NodeManagerInterface : TimelineFramesModel::NodeManipulationInterface { NodeManagerInterface(KisNodeManager *manager) : m_manager(manager) {} KisLayerSP addPaintLayer() const override { return m_manager->createPaintLayer(); } void removeNode(KisNodeSP node) const override { m_manager->removeSingleNode(node); } bool setNodeProperties(KisNodeSP node, KisImageSP image, KisBaseNode::PropertyList properties) const override { return m_manager->trySetNodeProperties(node, image, properties); } private: KisNodeManager *m_manager; }; void TimelineDocker::setCanvas(KoCanvasBase * canvas) { if (m_d->canvas == canvas) return; if (m_d->framesView) { m_d->framesView->slotCanvasUpdate(canvas); } if (m_d->framesModel->hasConnectionToCanvas()) { m_d->canvasConnections.clear(); m_d->framesModel->setDummiesFacade(0, 0, 0); m_d->framesModel->setFrameCache(0); m_d->framesModel->setAnimationPlayer(0); m_d->framesModel->setNodeManipulationInterface(0); } if (m_d->canvas) { m_d->canvas->disconnectCanvasObserver(this); m_d->canvas->animationPlayer()->disconnect(this); if(m_d->canvas->image()) { m_d->canvas->image()->animationInterface()->disconnect(this); } } m_d->canvas = dynamic_cast(canvas); setEnabled(m_d->canvas != 0); if(m_d->canvas) { KisDocument *doc = static_cast(m_d->canvas->imageView()->document()); KisShapeController *kritaShapeController = dynamic_cast(doc->shapeController()); m_d->framesModel->setDummiesFacade(kritaShapeController, m_d->canvas->image(), m_d->canvas->viewManager()->nodeManager()->nodeDisplayModeAdapter()); updateFrameCache(); { KisSignalsBlocker blocker(m_d->titlebar->sbStartFrame, m_d->titlebar->sbEndFrame, m_d->titlebar->sbFrameRate); KisImageAnimationInterface *animinterface = m_d->canvas->image()->animationInterface(); m_d->titlebar->sbStartFrame->setValue(animinterface->fullClipRange().start()); m_d->titlebar->sbEndFrame->setValue(animinterface->fullClipRange().end()); m_d->titlebar->sbFrameRate->setValue(animinterface->framerate()); m_d->titlebar->sbSpeed->setValue(100); m_d->titlebar->frameRegister->setValue(animinterface->currentTime()); } m_d->framesModel->setAnimationPlayer(m_d->canvas->animationPlayer()); m_d->framesModel->setNodeManipulationInterface( new NodeManagerInterface(m_d->canvas->viewManager()->nodeManager())); m_d->canvasConnections.addConnection( m_d->canvas->viewManager()->nodeManager(), SIGNAL(sigNodeActivated(KisNodeSP)), m_d->framesModel, SLOT(slotCurrentNodeChanged(KisNodeSP))); m_d->canvasConnections.addConnection( m_d->framesModel, SIGNAL(requestCurrentNodeChanged(KisNodeSP)), m_d->canvas->viewManager()->nodeManager(), SLOT(slotNonUiActivatedNode(KisNodeSP))); m_d->framesModel->slotCurrentNodeChanged(m_d->canvas->viewManager()->activeNode()); m_d->canvasConnections.addConnection( m_d->canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), this, SLOT(handleThemeChange())); m_d->canvasConnections.addConnection( m_d->canvas, SIGNAL(sigCanvasEngineChanged()), this, SLOT(updateFrameCache())); connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigUiTimeChanged(int)), this, SLOT(updateFrameRegister())); connect(m_d->canvas->animationPlayer(), SIGNAL(sigFrameChanged()), this, SLOT(updateFrameRegister())); connect(m_d->canvas->animationPlayer(), SIGNAL(sigPlaybackStopped()), this, SLOT(updateFrameRegister())); connect(m_d->canvas->animationPlayer(), SIGNAL(sigPlaybackStateChanged(bool)), m_d->titlebar->frameRegister, SLOT(setDisabled(bool))); connect(m_d->canvas->animationPlayer(), SIGNAL(sigPlaybackStateChanged(bool)), m_d->titlebar->transport, SLOT(setPlaying(bool))); connect(m_d->canvas->animationPlayer(), SIGNAL(sigPlaybackStatisticsUpdated()), this, SLOT(updatePlaybackStatistics())); connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigFullClipRangeChanged()), SLOT(handleClipRangeChange())); connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigFramerateChanged()), SLOT(handleFrameRateChange())); } } void TimelineDocker::handleThemeChange() { if (m_d->framesView) { m_d->framesView->slotUpdateIcons(); } } void TimelineDocker::updateFrameCache() { m_d->framesModel->setFrameCache(m_d->canvas->frameCache()); } void TimelineDocker::updateFrameRegister() { if (!m_d->canvas && !m_d->canvas->image()) { return; } const int frame = m_d->canvas->animationPlayer()->isPlaying() ? m_d->canvas->animationPlayer()->visibleFrame() : m_d->canvas->image()->animationInterface()->currentUITime(); m_d->titlebar->frameRegister->setValue(frame); } void TimelineDocker::updatePlaybackStatistics() { qreal effectiveFps = 0.0; qreal realFps = 0.0; qreal framesDropped = 0.0; bool isPlaying = false; KisAnimationPlayer *player = m_d->canvas && m_d->canvas->animationPlayer() ? m_d->canvas->animationPlayer() : 0; if (player) { effectiveFps = player->effectiveFps(); realFps = player->realFps(); framesDropped = player->framesDroppedPortion(); isPlaying = player->isPlaying(); } KisConfig cfg(true); const bool shouldDropFrames = cfg.animationDropFrames(); QAction *action = m_d->titlebar->btnDropFrames->defaultAction(); const bool droppingFrames = shouldDropFrames && framesDropped > 0.05; action->setIcon(KisIconUtils::loadIcon(droppingFrames ? "droppedframes" : "dropframe")); QString actionText; if (!isPlaying) { actionText = QString("%1 (%2) \n%3") .arg(KisAnimationUtils::dropFramesActionName) .arg(KritaUtils::toLocalizedOnOff(shouldDropFrames)) .arg(i18n("Enable to preserve playback timing.")); } else { actionText = QString("%1 (%2)\n" "%3\n" "%4\n" "%5") .arg(KisAnimationUtils::dropFramesActionName) .arg(KritaUtils::toLocalizedOnOff(shouldDropFrames)) .arg(i18n("Effective FPS:\t%1", effectiveFps)) .arg(i18n("Real FPS:\t%1", realFps)) .arg(i18n("Frames dropped:\t%1\%", framesDropped * 100)); } action->setText(actionText); } void TimelineDocker::unsetCanvas() { setCanvas(0); } void TimelineDocker::setViewManager(KisViewManager *view) { m_d->mainWindow = view->mainWindow(); KisActionManager *actionManager = view->actionManager(); m_d->framesView->setActionManager(actionManager); KisAction *action = 0; TimelineDockerTitleBar* titleBar = static_cast(titleBarWidget()); action = actionManager->actionByName("add_blank_frame"); titleBar->btnAddKeyframe->setDefaultAction(action); action = actionManager->actionByName("add_duplicate_frame"); titleBar->btnDuplicateKeyframe->setDefaultAction(action); action = actionManager->actionByName("remove_frames"); titleBar->btnRemoveKeyframe->setDefaultAction(action); action = actionManager->createAction("toggle_playback"); action->setActivationFlags(KisAction::ACTIVE_IMAGE); connect(action, SIGNAL(triggered(bool)), SLOT(playPause())); action = actionManager->createAction("stop_playback"); action->setActivationFlags(KisAction::ACTIVE_IMAGE); connect(action, SIGNAL(triggered(bool)), SLOT(stop())); action = actionManager->createAction("previous_frame"); action->setActivationFlags(KisAction::ACTIVE_IMAGE); connect(action, SIGNAL(triggered(bool)), SLOT(previousFrame())); action = actionManager->createAction("next_frame"); action->setActivationFlags(KisAction::ACTIVE_IMAGE); connect(action, SIGNAL(triggered(bool)), SLOT(nextFrame())); + action = actionManager->createAction("previous_keyframe"); + action->setActivationFlags(KisAction::ACTIVE_IMAGE); + connect(action, SIGNAL(triggered(bool)), SLOT(previousKeyframe())); + + action = actionManager->createAction("next_keyframe"); + action->setActivationFlags(KisAction::ACTIVE_IMAGE); + connect(action, SIGNAL(triggered(bool)), SLOT(nextKeyframe())); + + action = actionManager->createAction("previous_matching_keyframe"); + action->setActivationFlags(KisAction::ACTIVE_IMAGE); + connect(action, SIGNAL(triggered(bool)), SLOT(previousMatchingKeyframe())); + + action = actionManager->createAction("next_matching_keyframe"); + action->setActivationFlags(KisAction::ACTIVE_IMAGE); + connect(action, SIGNAL(triggered(bool)), SLOT(nextMatchingKeyframe())); + action = actionManager->createAction("auto_key"); m_d->titlebar->btnAutoFrame->setDefaultAction(action); connect(action, SIGNAL(triggered(bool)), SLOT(setAutoKey(bool))); { KisImageConfig config(true); action->setChecked(config.autoKeyEnabled()); action->setIcon(config.autoKeyEnabled() ? KisIconUtils::loadIcon("auto-key-on") : KisIconUtils::loadIcon("auto-key-off")); } action = actionManager->createAction("drop_frames"); m_d->titlebar->btnDropFrames->setDefaultAction(action); connect(action, SIGNAL(triggered(bool)), SLOT(setDropFrames(bool))); { KisConfig config(true); action->setChecked(config.animationDropFrames()); } } void TimelineDocker::playPause() { if (!m_d->canvas) return; if (m_d->canvas->animationPlayer()->isPlaying()) { m_d->canvas->animationPlayer()->pause(); } else { m_d->canvas->animationPlayer()->play(); } } void TimelineDocker::stop() { if (!m_d->canvas) return; if( m_d->canvas->animationPlayer()->isStopped()) { m_d->canvas->animationPlayer()->goToStartFrame(); } else { m_d->canvas->animationPlayer()->stop(); m_d->canvas->animationPlayer()->goToPlaybackOrigin(); } } void TimelineDocker::previousFrame() { if (!m_d->canvas) return; KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); int time = animInterface->currentUITime() - 1; if (time >= 0) { animInterface->requestTimeSwitchWithUndo(time); } } void TimelineDocker::nextFrame() { if (!m_d->canvas) return; KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); int time = animInterface->currentUITime() + 1; animInterface->requestTimeSwitchWithUndo(time); } +void TimelineDocker::previousKeyframe() +{ + if (!m_d->canvas) return; + + KisNodeSP node = m_d->canvas->viewManager()->activeNode(); + if (!node) return; + + KisKeyframeChannel *keyframes = + node->getKeyframeChannel(KisKeyframeChannel::Content.id()); + if (!keyframes) return; + + KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); + int time = animInterface->currentUITime(); + + KisKeyframeSP currentKeyframe = keyframes->keyframeAt(time); + KisKeyframeSP destinationKeyframe; + + if (!currentKeyframe) { + destinationKeyframe = keyframes->activeKeyframeAt(time); + } else { + destinationKeyframe = keyframes->previousKeyframe(currentKeyframe); + } + + if (destinationKeyframe) { + animInterface->requestTimeSwitchWithUndo(destinationKeyframe->time()); + } +} + +void TimelineDocker::nextKeyframe() +{ + if (!m_d->canvas) return; + + KisNodeSP node = m_d->canvas->viewManager()->activeNode(); + if (!node) return; + + KisKeyframeChannel *keyframes = + node->getKeyframeChannel(KisKeyframeChannel::Content.id()); + if (!keyframes) return; + + KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface(); + int time = animation->currentUITime(); + + KisKeyframeSP currentKeyframe = keyframes->activeKeyframeAt(time); + KisKeyframeSP destinationKeyframe; + + if (currentKeyframe) { + destinationKeyframe = keyframes->nextKeyframe(currentKeyframe); + } + + if (destinationKeyframe) { + animation->requestTimeSwitchWithUndo(destinationKeyframe->time()); + } +} + +void TimelineDocker::previousMatchingKeyframe() +{ + if (!m_d->canvas) return; + + KisNodeSP node = m_d->canvas->viewManager()->activeNode(); + if (!node) return; + + KisKeyframeChannel *keyframes = + node->getKeyframeChannel(KisKeyframeChannel::Content.id()); + if (!keyframes) return; + + KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); + int time = animInterface->currentUITime(); + + KisKeyframeSP currentKeyframe = keyframes->keyframeAt(time); + KisKeyframeSP destinationKeyframe = keyframes->activeKeyframeAt(time); + const int desiredColor = currentKeyframe ? currentKeyframe->colorLabel() : destinationKeyframe->colorLabel(); + while (destinationKeyframe && + (currentKeyframe == destinationKeyframe || destinationKeyframe->colorLabel() != desiredColor)) { + destinationKeyframe = keyframes->previousKeyframe(destinationKeyframe); + } + + if (destinationKeyframe) { + animInterface->requestTimeSwitchWithUndo(destinationKeyframe->time()); + } + +} + +void TimelineDocker::nextMatchingKeyframe() +{ + if (!m_d->canvas) return; + + KisNodeSP node = m_d->canvas->viewManager()->activeNode(); + if (!node) return; + + KisKeyframeChannel *keyframes = + node->getKeyframeChannel(KisKeyframeChannel::Content.id()); + if (!keyframes) return; + + KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface(); + int time = animation->currentUITime(); + + KisKeyframeSP currentKeyframe = keyframes->activeKeyframeAt(time); + + if (!currentKeyframe) { + return; + } + + KisKeyframeSP destinationKeyframe = currentKeyframe; + const int desiredColor = currentKeyframe->colorLabel(); + + while ( destinationKeyframe && + (currentKeyframe == destinationKeyframe || destinationKeyframe->colorLabel() != desiredColor)){ + destinationKeyframe = keyframes->nextKeyframe(destinationKeyframe); + } + + if (destinationKeyframe) { + animation->requestTimeSwitchWithUndo(destinationKeyframe->time()); + } +} + void TimelineDocker::goToFrame(int frameIndex) { if (!m_d->canvas || !m_d->canvas->image()) return; KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); if (m_d->canvas->animationPlayer()->isPlaying() || frameIndex == animInterface->currentUITime()) { return; } animInterface->requestTimeSwitchWithUndo(frameIndex); } void TimelineDocker::setStartFrame(int frame) { if (!m_d->canvas || !m_d->canvas->image()) return; KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); animInterface->setFullClipRangeStartTime(frame); } void TimelineDocker::setEndFrame(int frame) { if (!m_d->canvas || !m_d->canvas->image()) return; KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); animInterface->setFullClipRangeEndTime(frame); } void TimelineDocker::setFrameRate(int framerate) { if (!m_d->canvas || !m_d->canvas->image()) return; KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); animInterface->setFramerate(framerate); } void TimelineDocker::setPlaybackSpeed(int playbackSpeed) { if (!m_d->canvas || !m_d->canvas->image()) return; const float normalizedSpeed = playbackSpeed / 100.0; m_d->canvas->animationPlayer()->slotUpdatePlaybackSpeed(normalizedSpeed); } void TimelineDocker::setDropFrames(bool dropFrames) { KisConfig cfg(false); if (dropFrames != cfg.animationDropFrames()) { cfg.setAnimationDropFrames(dropFrames); updatePlaybackStatistics(); } } void TimelineDocker::setAutoKey(bool autoKey) { KisImageConfig cfg(false); if (autoKey != cfg.autoKeyEnabled()) { cfg.setAutoKeyEnabled(autoKey); const QIcon icon = cfg.autoKeyEnabled() ? KisIconUtils::loadIcon("auto-key-on") : KisIconUtils::loadIcon("auto-key-off"); QAction* action = m_d->titlebar->btnAutoFrame->defaultAction(); action->setIcon(icon); } } void TimelineDocker::handleClipRangeChange() { if (!m_d->canvas || !m_d->canvas->image()) return; KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); m_d->titlebar->sbStartFrame->setValue(animInterface->fullClipRange().start()); m_d->titlebar->sbEndFrame->setValue(animInterface->fullClipRange().end()); } void TimelineDocker::handleFrameRateChange() { if (!m_d->canvas || !m_d->canvas->image()) return; KisImageAnimationInterface *animInterface = m_d->canvas->image()->animationInterface(); m_d->titlebar->sbFrameRate->setValue( m_d->canvas->image()->animationInterface()->framerate() ); } diff --git a/plugins/dockers/animation/timeline_docker.h b/plugins/dockers/animation/timeline_docker.h index b59f548ab4..c1effad366 100644 --- a/plugins/dockers/animation/timeline_docker.h +++ b/plugins/dockers/animation/timeline_docker.h @@ -1,135 +1,139 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * Copyright (c) 2020 Emmet O'Neill * Copyright (c) 2020 Eoin O'Neill * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _TIMELINE_DOCKER_H_ #define _TIMELINE_DOCKER_H_ #include "kritaimage_export.h" #include #include #include #include #ifdef Q_OS_MACOS #include #endif class QPushButton; class QToolButton; class KisTransportControls; class KisIntParseSpinBox; class KisSliderSpinBox; class KisCanvas2; class KisAction; /* * A customized titlebar for the Animation Timeline Docker that's * packed with useful widgets and menus. * * To avoid cluttering the UI, elements that are important to the * animator's workflow should be available at a glace, while * set-and-forget types of things should be hidden inside of menus. */ class TimelineDockerTitleBar : public KisUtilityTitleBar { Q_OBJECT public: TimelineDockerTitleBar(QWidget *parent = nullptr); KisTransportControls* transport; KisIntParseSpinBox *frameRegister; QToolButton *btnAddKeyframe; QToolButton *btnDuplicateKeyframe; QToolButton *btnRemoveKeyframe; QPushButton *btnOnionSkinsMenu; QPushButton *btnAudioMenu; QToolButton *btnSettingsMenu; KisIntParseSpinBox *sbStartFrame; KisIntParseSpinBox *sbEndFrame; KisIntParseSpinBox *sbFrameRate; KisSliderSpinBox *sbSpeed; QToolButton *btnAutoFrame; QToolButton *btnDropFrames; private: const int MAX_FRAMES = 9999; }; /* * Krita's Animation Timeline Docker. * This is the GUI heart of Krita's traditional animation workflow, * and is where artists can configure, edit, scrub and play their animation. * * Currently interacts with the TimelinFramesView/Model as well as * the KisImageAnimationInterface. (TODO: Consider refactoring to * streamline this interaction towards Docker -> AnimationPlayer -> ImageAnimInterface) */ class TimelineDocker : public QDockWidget, public KisMainwindowObserver { Q_OBJECT public: TimelineDocker(); ~TimelineDocker() override; QString observerName() override { return "AnimationTimelineDocker"; } void setCanvas(KoCanvasBase *canvas) override; void unsetCanvas() override; void setViewManager(KisViewManager *kisview) override; public Q_SLOTS: void playPause(); void stop(); void previousFrame(); void nextFrame(); + void previousKeyframe(); + void nextKeyframe(); + void previousMatchingKeyframe(); + void nextMatchingKeyframe(); void goToFrame(int frameIndex); void setStartFrame(int frame); void setEndFrame(int frame); void setFrameRate(int frmaerate); void setPlaybackSpeed(int playbackSpeed); void setDropFrames(bool dropFrames); void setAutoKey(bool autoKey); void handleClipRangeChange(); void handleFrameRateChange(); void updateFrameCache(); void updateFrameRegister(); void updatePlaybackStatistics(); void handleThemeChange(); private: struct Private; const QScopedPointer m_d; }; #endif diff --git a/plugins/dockers/layerdocker/LayerBox.cpp b/plugins/dockers/layerdocker/LayerBox.cpp index f1a93865af..2e509a5aed 100644 --- a/plugins/dockers/layerdocker/LayerBox.cpp +++ b/plugins/dockers/layerdocker/LayerBox.cpp @@ -1,1176 +1,1195 @@ /* * LayerBox.cc - part of Krita aka Krayon aka KimageShop * * Copyright (c) 2002 Patrick Julien * Copyright (C) 2006 Gábor Lehel * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Boudewijn Rempt * Copyright (c) 2011 José Luis Vergara * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "LayerBox.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_action_manager.h" #include "widgets/kis_cmb_composite.h" #include "kis_slider_spin_box.h" #include "KisViewManager.h" #include "kis_node_manager.h" #include "kis_node_model.h" #include "canvas/kis_canvas2.h" #include "kis_dummies_facade_base.h" #include "kis_shape_controller.h" #include "kis_selection_mask.h" #include "kis_config.h" #include "KisView.h" #include "krita_utils.h" #include "kis_color_label_selector_widget.h" #include "kis_signals_blocker.h" #include "kis_color_filter_combo.h" #include "kis_node_filter_proxy_model.h" #include "kis_selection.h" #include "kis_processing_applicator.h" #include "commands/kis_set_global_selection_command.h" #include "KisSelectionActionsAdapter.h" #include "kis_layer_utils.h" #include "ui_WdgLayerBox.h" #include "NodeView.h" #include "SyncButtonAndAction.h" class LayerBoxStyle : public QProxyStyle { public: LayerBoxStyle(QStyle *baseStyle = 0) : QProxyStyle(baseStyle) {} void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { if (element == QStyle::PE_IndicatorItemViewItemDrop) { QColor color(widget->palette().color(QPalette::Highlight).lighter()); if (option->rect.height() == 0) { QBrush brush(color); QRect r(option->rect); r.setTop(r.top() - 2); r.setBottom(r.bottom() + 2); painter->fillRect(r, brush); } else { color.setAlpha(200); QBrush brush(color); painter->fillRect(option->rect, brush); } } else { QProxyStyle::drawPrimitive(element, option, painter, widget); } } }; inline void LayerBox::connectActionToButton(KisViewManager* viewManager, QAbstractButton *button, const QString &id) { if (!viewManager || !button) return; KisAction *action = viewManager->actionManager()->actionByName(id); if (!action) return; connect(button, SIGNAL(clicked()), action, SLOT(trigger())); connect(action, SIGNAL(sigEnableSlaves(bool)), button, SLOT(setEnabled(bool))); connect(viewManager->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateIcons())); } inline void LayerBox::addActionToMenu(QMenu *menu, const QString &id) { if (m_canvas) { menu->addAction(m_canvas->viewManager()->actionManager()->actionByName(id)); } } LayerBox::LayerBox() : QDockWidget(i18n("Layers")) , m_canvas(0) , m_wdgLayerBox(new Ui_WdgLayerBox) , m_thumbnailCompressor(500, KisSignalCompressor::FIRST_INACTIVE) , m_colorLabelCompressor(500, KisSignalCompressor::FIRST_INACTIVE) , m_thumbnailSizeCompressor(100, KisSignalCompressor::FIRST_INACTIVE) { KisConfig cfg(false); QWidget* mainWidget = new QWidget(this); setWidget(mainWidget); m_opacityDelayTimer.setSingleShot(true); m_wdgLayerBox->setupUi(mainWidget); m_wdgLayerBox->listLayers->setStyle(new LayerBoxStyle(m_wdgLayerBox->listLayers->style())); connect(m_wdgLayerBox->listLayers, SIGNAL(contextMenuRequested(QPoint,QModelIndex)), this, SLOT(slotContextMenuRequested(QPoint,QModelIndex))); connect(m_wdgLayerBox->listLayers, SIGNAL(collapsed(QModelIndex)), SLOT(slotCollapsed(QModelIndex))); connect(m_wdgLayerBox->listLayers, SIGNAL(expanded(QModelIndex)), SLOT(slotExpanded(QModelIndex))); connect(m_wdgLayerBox->listLayers, SIGNAL(selectionChanged(QModelIndexList)), SLOT(selectionChanged(QModelIndexList))); slotUpdateIcons(); m_wdgLayerBox->bnDelete->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnRaise->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnLower->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnProperties->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnDuplicate->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnLower->setEnabled(false); m_wdgLayerBox->bnRaise->setEnabled(false); if (cfg.sliderLabels()) { m_wdgLayerBox->opacityLabel->hide(); m_wdgLayerBox->doubleOpacity->setPrefix(QString("%1: ").arg(i18n("Opacity"))); } m_wdgLayerBox->doubleOpacity->setRange(0, 100, 0); m_wdgLayerBox->doubleOpacity->setSuffix(i18n("%")); connect(m_wdgLayerBox->doubleOpacity, SIGNAL(valueChanged(qreal)), SLOT(slotOpacitySliderMoved(qreal))); connect(&m_opacityDelayTimer, SIGNAL(timeout()), SLOT(slotOpacityChanged())); connect(m_wdgLayerBox->cmbComposite, SIGNAL(activated(int)), SLOT(slotCompositeOpChanged(int))); m_newLayerMenu = new QMenu(this); m_wdgLayerBox->bnAdd->setMenu(m_newLayerMenu); m_wdgLayerBox->bnAdd->setPopupMode(QToolButton::MenuButtonPopup); m_nodeModel = new KisNodeModel(this); m_filteringModel = new KisNodeFilterProxyModel(this); m_filteringModel->setNodeModel(m_nodeModel); /** * Connect model updateUI() to enable/disable controls. * Note: nodeActivated() is connected separately in setImage(), because * it needs particular order of calls: first the connection to the * node manager should be called, then updateUI() */ connect(m_nodeModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotModelReset())); connect(m_nodeModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); connect(m_nodeModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); connect(m_nodeModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); KisAction *showGlobalSelectionMask = new KisAction(i18n("&Show Global Selection Mask"), this); showGlobalSelectionMask->setObjectName("show-global-selection-mask"); showGlobalSelectionMask->setActivationFlags(KisAction::ACTIVE_IMAGE); showGlobalSelectionMask->setToolTip(i18nc("@info:tooltip", "Shows global selection as a usual selection mask in Layers docker")); showGlobalSelectionMask->setCheckable(true); connect(showGlobalSelectionMask, SIGNAL(triggered(bool)), SLOT(slotEditGlobalSelection(bool))); m_actions.append(showGlobalSelectionMask); showGlobalSelectionMask->setChecked(cfg.showGlobalSelection()); m_colorSelector = new KisColorLabelSelectorWidget(this); MouseClickIgnore* mouseEater = new MouseClickIgnore(this); m_colorSelector->installEventFilter(mouseEater); connect(m_colorSelector, SIGNAL(currentIndexChanged(int)), SLOT(slotColorLabelChanged(int))); m_colorSelectorAction = new QWidgetAction(this); m_colorSelectorAction->setDefaultWidget(m_colorSelector); connect(m_nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), &m_colorLabelCompressor, SLOT(start())); m_wdgLayerBox->listLayers->setModel(m_filteringModel); // this connection should be done *after* the setModel() call to // happen later than the internal selection model connect(m_filteringModel.data(), &KisNodeFilterProxyModel::rowsAboutToBeRemoved, this, &LayerBox::slotAboutToRemoveRows); //LayerFilter Menu QMenu *layerFilterMenu = new QMenu(this); m_wdgLayerBox->bnLayerFilters->setMenu(layerFilterMenu); m_wdgLayerBox->bnLayerFilters->setPopupMode(QToolButton::InstantPopup); const QIcon filterIcon = KisIconUtils::loadIcon("view-filter"); m_wdgLayerBox->bnLayerFilters->setIcon(filterIcon); QPixmap filterEnabledPixmap = filterIcon.pixmap(64,64); const QBitmap filterEnabledBitmask = filterEnabledPixmap.mask(); filterEnabledPixmap.fill(palette().color(QPalette::Highlight)); filterEnabledPixmap.setMask(filterEnabledBitmask); const QIcon filterEnabledIcon = QIcon(filterEnabledPixmap); layerFilterWidget = new KisLayerFilterWidget(this); connect(layerFilterWidget, SIGNAL(filteringOptionsChanged()), this, SLOT(updateLayerFiltering())); connect(layerFilterWidget, &KisLayerFilterWidget::filteringOptionsChanged, [this, filterIcon, filterEnabledIcon](){ if(layerFilterWidget->isCurrentlyFiltering()) { m_wdgLayerBox->bnLayerFilters->setIcon(filterEnabledIcon); } else { m_wdgLayerBox->bnLayerFilters->setIcon(filterIcon); } m_wdgLayerBox->bnLayerFilters->setSelectedColors(QList::fromSet(layerFilterWidget->getActiveColors())); m_wdgLayerBox->bnLayerFilters->setTextFilter(layerFilterWidget->hasTextFilter()); }); QWidgetAction *layerFilterMenuAction = new QWidgetAction(this); layerFilterMenuAction->setDefaultWidget(layerFilterWidget); layerFilterMenu->addAction(layerFilterMenuAction); setEnabled(false); connect(&m_thumbnailCompressor, SIGNAL(timeout()), SLOT(updateThumbnail())); connect(&m_colorLabelCompressor, SIGNAL(timeout()), SLOT(updateAvailableLabels())); // set up the configure menu for changing thumbnail size QMenu* configureMenu = new QMenu(this); configureMenu->setStyleSheet("margin: 6px"); configureMenu->addSection(i18n("Thumbnail Size")); m_wdgLayerBox->configureLayerDockerToolbar->setMenu(configureMenu); m_wdgLayerBox->configureLayerDockerToolbar->setIcon(KisIconUtils::loadIcon("configure")); m_wdgLayerBox->configureLayerDockerToolbar->setPopupMode(QToolButton::InstantPopup); // add horizontal slider thumbnailSizeSlider = new QSlider(this); thumbnailSizeSlider->setOrientation(Qt::Horizontal); thumbnailSizeSlider->setRange(20, 80); thumbnailSizeSlider->setValue(cfg.layerThumbnailSize(false)); // grab this from the kritarc thumbnailSizeSlider->setMinimumHeight(20); thumbnailSizeSlider->setMinimumWidth(40); thumbnailSizeSlider->setTickInterval(5); QWidgetAction *sliderAction= new QWidgetAction(this); sliderAction->setDefaultWidget(thumbnailSizeSlider); configureMenu->addAction(sliderAction); connect(thumbnailSizeSlider, SIGNAL(sliderMoved(int)), &m_thumbnailSizeCompressor, SLOT(start())); connect(&m_thumbnailSizeCompressor, SIGNAL(timeout()), SLOT(slotUpdateThumbnailIconSize())); } LayerBox::~LayerBox() { delete m_wdgLayerBox; } void expandNodesRecursively(KisNodeSP root, QPointer filteringModel, NodeView *nodeView) { if (!root) return; if (filteringModel.isNull()) return; if (!nodeView) return; nodeView->blockSignals(true); KisNodeSP node = root->firstChild(); while (node) { QModelIndex idx = filteringModel->indexFromNode(node); if (idx.isValid()) { nodeView->setExpanded(idx, !node->collapsed()); } if (!node->collapsed() && node->childCount() > 0) { expandNodesRecursively(node, filteringModel, nodeView); } node = node->nextSibling(); } nodeView->blockSignals(false); } void LayerBox::slotAddLayerBnClicked() { if (m_canvas) { KisNodeList nodes = m_nodeManager->selectedNodes(); if (nodes.size() == 1) { KisAction *action = m_canvas->viewManager()->actionManager()->actionByName("add_new_paint_layer"); action->trigger(); } else { KisAction *action = m_canvas->viewManager()->actionManager()->actionByName("create_quick_group"); action->trigger(); } } } void LayerBox::setViewManager(KisViewManager* kisview) { m_nodeManager = kisview->nodeManager(); if (m_nodeManager) { connect(m_nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); } Q_FOREACH (KisAction *action, m_actions) { kisview->actionManager()-> addAction(action->objectName(), action); } connect(m_wdgLayerBox->bnAdd, SIGNAL(clicked()), this, SLOT(slotAddLayerBnClicked())); connectActionToButton(kisview, m_wdgLayerBox->bnDuplicate, "duplicatelayer"); KisActionManager *actionManager = kisview->actionManager(); KisAction *action = actionManager->createAction("RenameCurrentLayer"); Q_ASSERT(action); connect(action, SIGNAL(triggered()), this, SLOT(slotRenameCurrentNode())); m_propertiesAction = actionManager->createAction("layer_properties"); Q_ASSERT(m_propertiesAction); new SyncButtonAndAction(m_propertiesAction, m_wdgLayerBox->bnProperties, this); connect(m_propertiesAction, SIGNAL(triggered()), this, SLOT(slotPropertiesClicked())); m_removeAction = actionManager->createAction("remove_layer"); Q_ASSERT(m_removeAction); new SyncButtonAndAction(m_removeAction, m_wdgLayerBox->bnDelete, this); connect(m_removeAction, SIGNAL(triggered()), this, SLOT(slotRmClicked())); action = actionManager->createAction("move_layer_up"); Q_ASSERT(action); new SyncButtonAndAction(action, m_wdgLayerBox->bnRaise, this); connect(action, SIGNAL(triggered()), this, SLOT(slotRaiseClicked())); action = actionManager->createAction("move_layer_down"); Q_ASSERT(action); new SyncButtonAndAction(action, m_wdgLayerBox->bnLower, this); connect(action, SIGNAL(triggered()), this, SLOT(slotLowerClicked())); m_changeCloneSourceAction = actionManager->createAction("set-copy-from"); Q_ASSERT(m_changeCloneSourceAction); connect(m_changeCloneSourceAction, &KisAction::triggered, this, &LayerBox::slotChangeCloneSourceClicked); + + m_layerToggleSolo = actionManager->createAction("toggle_layer_soloing"); + connect(m_layerToggleSolo, SIGNAL(triggered(bool)), this, SLOT(toggleActiveLayerSolo())); } void LayerBox::setCanvas(KoCanvasBase *canvas) { if (m_canvas == canvas) return; setEnabled(canvas != 0); if (m_canvas) { m_canvas->disconnectCanvasObserver(this); m_nodeModel->setDummiesFacade(0, 0, 0, 0, 0); m_selectionActionsAdapter.reset(); if (m_image) { KisImageAnimationInterface *animation = m_image->animationInterface(); animation->disconnect(this); } disconnect(m_image, 0, this, 0); disconnect(m_nodeManager, 0, this, 0); disconnect(m_nodeModel, 0, m_nodeManager, 0); m_nodeManager->slotSetSelectedNodes(KisNodeList()); } m_canvas = dynamic_cast(canvas); if (m_canvas) { m_image = m_canvas->image(); emit imageChanged(); connect(m_image, SIGNAL(sigImageUpdated(QRect)), &m_thumbnailCompressor, SLOT(start())); KisDocument* doc = static_cast(m_canvas->imageView()->document()); KisShapeController *kritaShapeController = dynamic_cast(doc->shapeController()); KisDummiesFacadeBase *kritaDummiesFacade = static_cast(kritaShapeController); m_selectionActionsAdapter.reset(new KisSelectionActionsAdapter(m_canvas->viewManager()->selectionManager())); m_nodeModel->setDummiesFacade(kritaDummiesFacade, m_image, kritaShapeController, m_selectionActionsAdapter.data(), m_nodeManager); connect(m_image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted())); connect(m_image, SIGNAL(sigNodeCollapsedChanged()), SLOT(slotNodeCollapsedChanged())); // cold start if (m_nodeManager) { setCurrentNode(m_nodeManager->activeNode()); // Connection KisNodeManager -> LayerBox connect(m_nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)), this, SLOT(setCurrentNode(KisNodeSP))); connect(m_nodeManager, SIGNAL(sigUiNeedChangeSelectedNodes(QList)), SLOT(slotNodeManagerChangedSelection(QList))); } else { setCurrentNode(m_canvas->imageView()->currentNode()); } // Connection LayerBox -> KisNodeManager (isolate layer) connect(m_nodeModel, SIGNAL(toggleIsolateActiveNode()), m_nodeManager, SLOT(toggleIsolateActiveNode())); KisImageAnimationInterface *animation = m_image->animationInterface(); connect(animation, &KisImageAnimationInterface::sigUiTimeChanged, this, &LayerBox::slotImageTimeChanged); expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers); m_wdgLayerBox->listLayers->scrollTo(m_wdgLayerBox->listLayers->currentIndex()); updateAvailableLabels(); addActionToMenu(m_newLayerMenu, "add_new_paint_layer"); addActionToMenu(m_newLayerMenu, "add_new_group_layer"); addActionToMenu(m_newLayerMenu, "add_new_clone_layer"); addActionToMenu(m_newLayerMenu, "add_new_shape_layer"); addActionToMenu(m_newLayerMenu, "add_new_adjustment_layer"); addActionToMenu(m_newLayerMenu, "add_new_fill_layer"); addActionToMenu(m_newLayerMenu, "add_new_file_layer"); m_newLayerMenu->addSeparator(); addActionToMenu(m_newLayerMenu, "add_new_transparency_mask"); addActionToMenu(m_newLayerMenu, "add_new_filter_mask"); addActionToMenu(m_newLayerMenu, "add_new_colorize_mask"); addActionToMenu(m_newLayerMenu, "add_new_transform_mask"); addActionToMenu(m_newLayerMenu, "add_new_selection_mask"); } } void LayerBox::unsetCanvas() { setEnabled(false); if (m_canvas) { m_newLayerMenu->clear(); } m_filteringModel->unsetDummiesFacade(); disconnect(m_image, 0, this, 0); disconnect(m_nodeManager, 0, this, 0); disconnect(m_nodeModel, 0, m_nodeManager, 0); m_nodeManager->slotSetSelectedNodes(KisNodeList()); m_canvas = 0; } void LayerBox::notifyImageDeleted() { setCanvas(0); } void LayerBox::updateUI() { if (!m_canvas) return; if (!m_nodeManager) return; KisNodeSP activeNode = m_nodeManager->activeNode(); if (activeNode != m_activeNode) { if( !m_activeNode.isNull() ) m_activeNode->disconnect(this); m_activeNode = activeNode; if (activeNode) { KisKeyframeChannel *opacityChannel = activeNode->getKeyframeChannel(KisKeyframeChannel::Opacity.id(), false); if (opacityChannel) { watchOpacityChannel(opacityChannel); } else { watchOpacityChannel(0); connect(activeNode.data(), &KisNode::keyframeChannelAdded, this, &LayerBox::slotKeyframeChannelAdded); } } } m_wdgLayerBox->bnRaise->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->nextSibling() || (activeNode->parent() && activeNode->parent() != m_image->root()))); m_wdgLayerBox->bnLower->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->prevSibling() || (activeNode->parent() && activeNode->parent() != m_image->root()))); m_wdgLayerBox->doubleOpacity->setEnabled(activeNode && activeNode->isEditable(false)); m_wdgLayerBox->cmbComposite->setEnabled(activeNode && activeNode->isEditable(false)); m_wdgLayerBox->cmbComposite->validate(m_image->colorSpace()); if (activeNode) { if (activeNode->inherits("KisColorizeMask") || activeNode->inherits("KisLayer")) { m_wdgLayerBox->doubleOpacity->setEnabled(true); if (!m_wdgLayerBox->doubleOpacity->isDragging()) { slotSetOpacity(activeNode->opacity() * 100.0 / 255); } const KoCompositeOp* compositeOp = activeNode->compositeOp(); if (compositeOp) { slotSetCompositeOp(compositeOp); } else { m_wdgLayerBox->cmbComposite->setEnabled(false); } const KisGroupLayer *group = qobject_cast(activeNode.data()); bool compositeSelectionActive = !(group && group->passThroughMode()); m_wdgLayerBox->cmbComposite->setEnabled(compositeSelectionActive); } else if (activeNode->inherits("KisMask")) { m_wdgLayerBox->cmbComposite->setEnabled(false); m_wdgLayerBox->doubleOpacity->setEnabled(false); } } } /** * This method is called *only* when non-GUI code requested the * change of the current node */ void LayerBox::setCurrentNode(KisNodeSP node) { m_filteringModel->setActiveNode(node); QModelIndex index = node ? m_filteringModel->indexFromNode(node) : QModelIndex(); m_filteringModel->setData(index, true, KisNodeModel::ActiveRole); updateUI(); } void LayerBox::slotModelReset() { if(m_nodeModel->hasDummiesFacade()) { QItemSelection selection; Q_FOREACH (const KisNodeSP node, m_nodeManager->selectedNodes()) { const QModelIndex &idx = m_filteringModel->indexFromNode(node); if(idx.isValid()){ QItemSelectionRange selectionRange(idx); selection << selectionRange; } } m_wdgLayerBox->listLayers->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); } updateUI(); } void LayerBox::slotSetCompositeOp(const KoCompositeOp* compositeOp) { KoID opId = KoCompositeOpRegistry::instance().getKoID(compositeOp->id()); m_wdgLayerBox->cmbComposite->blockSignals(true); m_wdgLayerBox->cmbComposite->selectCompositeOp(opId); m_wdgLayerBox->cmbComposite->blockSignals(false); } // range: 0-100 void LayerBox::slotSetOpacity(double opacity) { Q_ASSERT(opacity >= 0 && opacity <= 100); m_wdgLayerBox->doubleOpacity->blockSignals(true); m_wdgLayerBox->doubleOpacity->setValue(opacity); m_wdgLayerBox->doubleOpacity->blockSignals(false); } void LayerBox::slotContextMenuRequested(const QPoint &pos, const QModelIndex &index) { KisNodeList nodes = m_nodeManager->selectedNodes(); KisNodeSP activeNode = m_nodeManager->activeNode(); if (nodes.isEmpty() || !activeNode) return; if (m_canvas) { QMenu menu; const bool singleLayer = nodes.size() == 1; if (index.isValid()) { menu.addAction(m_propertiesAction); if (singleLayer) { addActionToMenu(&menu, "layer_style"); } Q_FOREACH(KisNodeSP node, nodes) { if (node && node->inherits("KisCloneLayer")) { menu.addAction(m_changeCloneSourceAction); break; } } { KisSignalsBlocker b(m_colorSelector); m_colorSelector->setCurrentIndex(singleLayer ? activeNode->colorLabelIndex() : -1); } menu.addAction(m_colorSelectorAction); menu.addSeparator(); addActionToMenu(&menu, "cut_layer_clipboard"); addActionToMenu(&menu, "copy_layer_clipboard"); addActionToMenu(&menu, "paste_layer_from_clipboard"); menu.addAction(m_removeAction); addActionToMenu(&menu, "duplicatelayer"); addActionToMenu(&menu, "merge_layer"); addActionToMenu(&menu, "new_from_visible"); if (singleLayer) { addActionToMenu(&menu, "flatten_image"); addActionToMenu(&menu, "flatten_layer"); } menu.addSeparator(); QMenu *selectMenu = menu.addMenu(i18n("&Select")); addActionToMenu(selectMenu, "select_all_layers"); addActionToMenu(selectMenu, "select_visible_layers"); addActionToMenu(selectMenu, "select_invisible_layers"); addActionToMenu(selectMenu, "select_locked_layers"); addActionToMenu(selectMenu, "select_unlocked_layers"); QMenu *groupMenu = menu.addMenu(i18n("&Group")); addActionToMenu(groupMenu, "create_quick_group"); addActionToMenu(groupMenu, "create_quick_clipping_group"); addActionToMenu(groupMenu, "quick_ungroup"); QMenu *locksMenu = menu.addMenu(i18n("&Toggle Locks && Visibility")); addActionToMenu(locksMenu, "toggle_layer_visibility"); addActionToMenu(locksMenu, "toggle_layer_lock"); addActionToMenu(locksMenu, "toggle_layer_inherit_alpha"); addActionToMenu(locksMenu, "toggle_layer_alpha_lock"); if (singleLayer) { QMenu *addLayerMenu = menu.addMenu(i18n("&Add")); addActionToMenu(addLayerMenu, "add_new_transparency_mask"); addActionToMenu(addLayerMenu, "add_new_filter_mask"); addActionToMenu(addLayerMenu, "add_new_colorize_mask"); addActionToMenu(addLayerMenu, "add_new_transform_mask"); addActionToMenu(addLayerMenu, "add_new_selection_mask"); addLayerMenu->addSeparator(); addActionToMenu(addLayerMenu, "add_new_clone_layer"); QMenu *convertToMenu = menu.addMenu(i18n("&Convert")); addActionToMenu(convertToMenu, "convert_to_paint_layer"); addActionToMenu(convertToMenu, "convert_to_transparency_mask"); addActionToMenu(convertToMenu, "convert_to_filter_mask"); addActionToMenu(convertToMenu, "convert_to_selection_mask"); addActionToMenu(convertToMenu, "convert_to_file_layer"); QMenu *splitAlphaMenu = menu.addMenu(i18n("S&plit Alpha")); addActionToMenu(splitAlphaMenu, "split_alpha_into_mask"); addActionToMenu(splitAlphaMenu, "split_alpha_write"); addActionToMenu(splitAlphaMenu, "split_alpha_save_merged"); } else { QMenu *addLayerMenu = menu.addMenu(i18n("&Add")); addActionToMenu(addLayerMenu, "add_new_clone_layer"); } menu.addSeparator(); addActionToMenu(&menu, "pin_to_timeline"); if (singleLayer) { KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node && !node->inherits("KisTransformMask")) { addActionToMenu(&menu, "isolate_active_layer"); addActionToMenu(&menu, "isolate_active_group"); } addActionToMenu(&menu, "selectopaque"); } } menu.exec(pos); } } void LayerBox::slotMinimalView() { m_wdgLayerBox->listLayers->setDisplayMode(NodeView::MinimalMode); } void LayerBox::slotDetailedView() { m_wdgLayerBox->listLayers->setDisplayMode(NodeView::DetailedMode); } void LayerBox::slotThumbnailView() { m_wdgLayerBox->listLayers->setDisplayMode(NodeView::ThumbnailMode); } void LayerBox::slotRmClicked() { if (!m_canvas) return; m_nodeManager->removeNode(); } void LayerBox::slotRaiseClicked() { if (!m_canvas) return; m_nodeManager->raiseNode(); } void LayerBox::slotLowerClicked() { if (!m_canvas) return; m_nodeManager->lowerNode(); } void LayerBox::slotPropertiesClicked() { if (!m_canvas) return; if (KisNodeSP active = m_nodeManager->activeNode()) { m_nodeManager->nodeProperties(active); } } void LayerBox::slotChangeCloneSourceClicked() { if (!m_canvas) return; m_nodeManager->changeCloneSource(); } void LayerBox::slotCompositeOpChanged(int index) { Q_UNUSED(index); if (!m_canvas) return; QString compositeOp = m_wdgLayerBox->cmbComposite->selectedCompositeOp().id(); m_nodeManager->nodeCompositeOpChanged(m_nodeManager->activeColorSpace()->compositeOp(compositeOp)); } void LayerBox::slotOpacityChanged() { if (!m_canvas) return; m_blockOpacityUpdate = true; m_nodeManager->nodeOpacityChanged(m_newOpacity); m_blockOpacityUpdate = false; } void LayerBox::slotOpacitySliderMoved(qreal opacity) { m_newOpacity = opacity; m_opacityDelayTimer.start(200); } void LayerBox::slotCollapsed(const QModelIndex &index) { KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node) { node->setCollapsed(true); } } void LayerBox::slotExpanded(const QModelIndex &index) { KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node) { node->setCollapsed(false); } } void LayerBox::slotSelectOpaque() { if (!m_canvas) return; QAction *action = m_canvas->viewManager()->actionManager()->actionByName("selectopaque"); if (action) { action->trigger(); } } void LayerBox::slotNodeCollapsedChanged() { expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers); } inline bool isSelectionMask(KisNodeSP node) { return dynamic_cast(node.data()); } KisNodeSP LayerBox::findNonHidableNode(KisNodeSP startNode) { if (KisNodeManager::isNodeHidden(startNode, true) && startNode->parent() && !startNode->parent()->parent()) { KisNodeSP node = startNode->prevSibling(); while (node && KisNodeManager::isNodeHidden(node, true)) { node = node->prevSibling(); } if (!node) { node = startNode->nextSibling(); while (node && KisNodeManager::isNodeHidden(node, true)) { node = node->nextSibling(); } } if (!node) { node = m_image->root()->lastChild(); while (node && KisNodeManager::isNodeHidden(node, true)) { node = node->prevSibling(); } } KIS_ASSERT_RECOVER_NOOP(node && "cannot activate any node!"); startNode = node; } return startNode; } void LayerBox::slotEditGlobalSelection(bool showSelections) { KisNodeSP lastActiveNode = m_nodeManager->activeNode(); KisNodeSP activateNode = lastActiveNode; KisSelectionMaskSP globalSelectionMask; if (!showSelections) { activateNode = m_savedNodeBeforeEditSelectionMode ? KisNodeSP(m_savedNodeBeforeEditSelectionMode) : findNonHidableNode(activateNode); } m_nodeModel->setShowGlobalSelection(showSelections); globalSelectionMask = m_image->rootLayer()->selectionMask(); // try to find deactivated, but visible masks if (!globalSelectionMask) { KoProperties properties; properties.setProperty("visible", true); QList masks = m_image->rootLayer()->childNodes(QStringList("KisSelectionMask"), properties); if (!masks.isEmpty()) { globalSelectionMask = dynamic_cast(masks.first().data()); } } // try to find at least any selection mask if (!globalSelectionMask) { KoProperties properties; QList masks = m_image->rootLayer()->childNodes(QStringList("KisSelectionMask"), properties); if (!masks.isEmpty()) { globalSelectionMask = dynamic_cast(masks.first().data()); } } if (globalSelectionMask) { if (showSelections) { activateNode = globalSelectionMask; } } if (activateNode != lastActiveNode) { m_nodeManager->slotNonUiActivatedNode(activateNode); } else if (lastActiveNode) { setCurrentNode(lastActiveNode); } if (showSelections && !globalSelectionMask) { KisProcessingApplicator applicator(m_image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Quick Selection Mask")); applicator.applyCommand( new KisLayerUtils::KeepNodesSelectedCommand( m_nodeManager->selectedNodes(), KisNodeList(), lastActiveNode, 0, m_image, false), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KisSetEmptyGlobalSelectionCommand(m_image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KisLayerUtils::SelectGlobalSelectionMask(m_image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); } else if (!showSelections && globalSelectionMask && globalSelectionMask->selection()->selectedRect().isEmpty()) { KisProcessingApplicator applicator(m_image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Cancel Quick Selection Mask")); applicator.applyCommand(new KisSetGlobalSelectionCommand(m_image, 0), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); } if (showSelections) { m_savedNodeBeforeEditSelectionMode = lastActiveNode; } } void LayerBox::selectionChanged(const QModelIndexList selection) { if (!m_nodeManager) return; /** * When the user clears the extended selection by clicking on the * empty area of the docker, the selection should be reset on to * the active layer, which might be even unselected(!). */ if (selection.isEmpty() && m_nodeManager->activeNode()) { QModelIndex selectedIndex = m_filteringModel->indexFromNode(m_nodeManager->activeNode()); m_wdgLayerBox->listLayers->selectionModel()-> setCurrentIndex(selectedIndex, QItemSelectionModel::ClearAndSelect); return; } QList selectedNodes; Q_FOREACH (const QModelIndex &idx, selection) { selectedNodes << m_filteringModel->nodeFromIndex(idx); } m_nodeManager->slotSetSelectedNodes(selectedNodes); updateUI(); } void LayerBox::slotAboutToRemoveRows(const QModelIndex &parent, int start, int end) { /** * Qt has changed its behavior when deleting an item. Previously * the selection priority was on the next item in the list, and * now it has shanged to the previous item. Here we just adjust * the selected item after the node removal. Please take care that * this method overrides what was done by the corresponding method * of QItemSelectionModel, which *has already done* its work. That * is why we use (start - 1) and (end + 1) in the activation * condition. * * See bug: https://bugs.kde.org/show_bug.cgi?id=345601 */ QModelIndex currentIndex = m_wdgLayerBox->listLayers->currentIndex(); QAbstractItemModel *model = m_filteringModel; if (currentIndex.isValid() && parent == currentIndex.parent() && currentIndex.row() >= start - 1 && currentIndex.row() <= end + 1) { QModelIndex old = currentIndex; if (model && end < model->rowCount(parent) - 1) // there are rows left below the change currentIndex = model->index(end + 1, old.column(), parent); else if (model && start > 0) // there are rows left above the change currentIndex = model->index(start - 1, old.column(), parent); else // there are no rows left in the table currentIndex = QModelIndex(); if (currentIndex.isValid() && currentIndex != old) { m_wdgLayerBox->listLayers->setCurrentIndex(currentIndex); } } } void LayerBox::slotNodeManagerChangedSelection(const KisNodeList &nodes) { if (!m_nodeManager) return; QModelIndexList newSelection; Q_FOREACH(KisNodeSP node, nodes) { newSelection << m_filteringModel->indexFromNode(node); } QItemSelectionModel *model = m_wdgLayerBox->listLayers->selectionModel(); if (KritaUtils::compareListsUnordered(newSelection, model->selectedIndexes())) { return; } QItemSelection selection; Q_FOREACH(const QModelIndex &idx, newSelection) { selection.select(idx, idx); } model->select(selection, QItemSelectionModel::ClearAndSelect); } void LayerBox::updateThumbnail() { m_wdgLayerBox->listLayers->updateNode(m_wdgLayerBox->listLayers->currentIndex()); } void LayerBox::slotRenameCurrentNode() { m_wdgLayerBox->listLayers->edit(m_wdgLayerBox->listLayers->currentIndex()); } void LayerBox::slotColorLabelChanged(int label) { KisNodeList selectedNodes = m_nodeManager->selectedNodes(); Q_FOREACH(KisNodeSP selectedNode, selectedNodes) { //Always apply label to selected nodes.. selectedNode->setColorLabelIndex(label); //Apply label only to unlabelled children.. KisNodeList children = selectedNode->childNodes(QStringList(), KoProperties()); auto applyLabelFunc = [label](KisNodeSP child) { if (child->colorLabelIndex() == 0) { child->setColorLabelIndex(label); } }; Q_FOREACH(KisNodeSP child, children) { KisLayerUtils::recursiveApplyNodes(child, applyLabelFunc); } } } void LayerBox::updateAvailableLabels() { if (!m_image) return; layerFilterWidget->updateColorLabels(m_image->root()); } void LayerBox::updateLayerFiltering() { m_filteringModel->setAcceptedLabels(layerFilterWidget->getActiveColors()); m_filteringModel->setTextFilter(layerFilterWidget->getTextFilter()); } void LayerBox::slotKeyframeChannelAdded(KisKeyframeChannel *channel) { if (channel->id() == KisKeyframeChannel::Opacity.id()) { watchOpacityChannel(channel); } } void LayerBox::watchOpacityChannel(KisKeyframeChannel *channel) { if (m_opacityChannel) { m_opacityChannel->disconnect(this); } m_opacityChannel = channel; if (m_opacityChannel) { connect(m_opacityChannel, SIGNAL(sigKeyframeAdded(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeRemoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeMoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeMoved(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeChanged(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); } } void LayerBox::slotOpacityKeyframeChanged(KisKeyframeSP keyframe) { Q_UNUSED(keyframe); if (m_blockOpacityUpdate) return; updateUI(); } void LayerBox::slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime) { Q_UNUSED(fromTime); slotOpacityKeyframeChanged(keyframe); } void LayerBox::slotImageTimeChanged(int time) { Q_UNUSED(time); updateUI(); } void LayerBox::slotForgetAboutSavedNodeBeforeEditSelectionMode() { m_savedNodeBeforeEditSelectionMode = 0; } void LayerBox::slotUpdateIcons() { m_wdgLayerBox->bnAdd->setIcon(KisIconUtils::loadIcon("addlayer")); m_wdgLayerBox->bnRaise->setIcon(KisIconUtils::loadIcon("arrowupblr")); m_wdgLayerBox->bnDelete->setIcon(KisIconUtils::loadIcon("deletelayer")); m_wdgLayerBox->bnLower->setIcon(KisIconUtils::loadIcon("arrowdown")); m_wdgLayerBox->bnProperties->setIcon(KisIconUtils::loadIcon("properties")); m_wdgLayerBox->bnDuplicate->setIcon(KisIconUtils::loadIcon("duplicatelayer")); // call child function about needing to update icons m_wdgLayerBox->listLayers->slotUpdateIcons(); } +void LayerBox::toggleActiveLayerSolo() { + NodeView* view = m_wdgLayerBox->listLayers; + if (!view) + return; + + KisNodeSP node = m_nodeManager->activeNode(); + if (!node) + return; + + QModelIndex index = m_filteringModel->indexFromNode(node); + if (!index.isValid()) + return; + + view->toggleSolo(index); +} + void LayerBox::slotUpdateThumbnailIconSize() { KisConfig cfg(false); cfg.setLayerThumbnailSize(thumbnailSizeSlider->value()); // this is a hack to force the layers list to update its display and // re-layout all the layers with the new thumbnail size resize(this->width()+1, this->height()+1); resize(this->width()-1, this->height()-1); } #include "moc_LayerBox.cpp" diff --git a/plugins/dockers/layerdocker/LayerBox.h b/plugins/dockers/layerdocker/LayerBox.h index 766564fbaf..d24187c133 100644 --- a/plugins/dockers/layerdocker/LayerBox.h +++ b/plugins/dockers/layerdocker/LayerBox.h @@ -1,209 +1,211 @@ /* * LayerBox.h - part of Krita aka Krayon aka KimageShop * * Copyright (c) 2002 Patrick Julien * Copyright (C) 2006 Gábor Lehel * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007-2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_LAYERBOX_H #define KIS_LAYERBOX_H #include #include #include #include #include #include #include #include #include #include "kis_action.h" #include "KisViewManager.h" #include "kis_mainwindow_observer.h" #include "kis_signal_compressor.h" #include "kis_layer_filter_widget.h" #include class QModelIndex; typedef QList QModelIndexList; class QMenu; class QAbstractButton; class KoCompositeOp; class KisCanvas2; class KisNodeModel; class KisNodeFilterProxyModel; class Ui_WdgLayerBox; class KisNodeJugglerCompressed; class KisColorLabelSelectorWidget; class QWidgetAction; class KisKeyframeChannel; class KisSelectionActionsAdapter; /** * A widget that shows a visualization of the layer structure. * * The center of the layer box is KisNodeModel, which shows the actual layers. * This widget adds docking functionality and command buttons. * */ class LayerBox : public QDockWidget, public KisMainwindowObserver { Q_OBJECT public: LayerBox(); ~LayerBox() override; QString observerName() override { return "LayerBox"; } /// reimplemented from KisMainwindowObserver void setViewManager(KisViewManager* kisview) override; void setCanvas(KoCanvasBase *canvas) override; void unsetCanvas() override; private Q_SLOTS: void notifyImageDeleted(); void slotContextMenuRequested(const QPoint &pos, const QModelIndex &index); void slotMinimalView(); void slotDetailedView(); void slotThumbnailView(); // From the node manager to the layerbox void slotSetCompositeOp(const KoCompositeOp* compositeOp); void slotSetOpacity(double opacity); void updateUI(); void setCurrentNode(KisNodeSP node); void slotModelReset(); // from the layerbox to the node manager void slotRmClicked(); void slotRaiseClicked(); void slotLowerClicked(); void slotPropertiesClicked(); void slotChangeCloneSourceClicked(); void slotCompositeOpChanged(int index); void slotOpacityChanged(); void slotOpacitySliderMoved(qreal opacity); void slotCollapsed(const QModelIndex &index); void slotExpanded(const QModelIndex &index); void slotSelectOpaque(); void slotNodeCollapsedChanged(); void slotEditGlobalSelection(bool showSelections); void slotRenameCurrentNode(); void slotAboutToRemoveRows(const QModelIndex &parent, int first, int last); void selectionChanged(const QModelIndexList selection); void slotNodeManagerChangedSelection(const QList &nodes); void slotColorLabelChanged(int index); void slotUpdateIcons(); + void toggleActiveLayerSolo(); void slotAddLayerBnClicked(); void updateThumbnail(); void updateAvailableLabels(); void updateLayerFiltering(); void slotUpdateThumbnailIconSize(); // Opacity keyframing void slotKeyframeChannelAdded(KisKeyframeChannel *channel); void slotOpacityKeyframeChanged(KisKeyframeSP keyframe); void slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime); void slotImageTimeChanged(int time); void slotForgetAboutSavedNodeBeforeEditSelectionMode(); Q_SIGNALS: void imageChanged(); private: inline void connectActionToButton(KisViewManager* view, QAbstractButton *button, const QString &id); inline void addActionToMenu(QMenu *menu, const QString &id); void watchOpacityChannel(KisKeyframeChannel *channel); KisNodeSP findNonHidableNode(KisNodeSP startNode); private: QPointer m_canvas; QScopedPointer m_selectionActionsAdapter; QMenu *m_newLayerMenu; KisImageWSP m_image; QPointer m_nodeModel; QPointer m_filteringModel; QPointer m_nodeManager; QPointer m_colorSelector; QPointer m_colorSelectorAction; Ui_WdgLayerBox* m_wdgLayerBox; QTimer m_opacityDelayTimer; int m_newOpacity; QVector m_actions; KisAction* m_removeAction; KisAction* m_propertiesAction; KisAction* m_changeCloneSourceAction; + KisAction* m_layerToggleSolo; KisSignalCompressor m_thumbnailCompressor; KisSignalCompressor m_colorLabelCompressor; KisSignalCompressor m_thumbnailSizeCompressor; KisLayerFilterWidget* layerFilterWidget; QSlider* thumbnailSizeSlider; KisNodeSP m_activeNode; KisNodeWSP m_savedNodeBeforeEditSelectionMode; QPointer m_opacityChannel; bool m_blockOpacityUpdate {false}; }; class LayerBoxFactory : public KoDockFactoryBase { public: LayerBoxFactory() { } QString id() const override { return QString("KisLayerBox"); } QDockWidget* createDockWidget() override { LayerBox * dockWidget = new LayerBox(); dockWidget->setObjectName(id()); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; #endif // KIS_LAYERBOX_H diff --git a/plugins/dockers/layerdocker/NodeDelegate.cpp b/plugins/dockers/layerdocker/NodeDelegate.cpp index f39402649a..c32ecd059d 100644 --- a/plugins/dockers/layerdocker/NodeDelegate.cpp +++ b/plugins/dockers/layerdocker/NodeDelegate.cpp @@ -1,1223 +1,1229 @@ /* Copyright (c) 2006 Gábor Lehel Copyright (c) 2008 Cyrille Berger Copyright (c) 2011 José Luis Vergara This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_config.h" #include "NodeDelegate.h" #include "kis_node_model.h" #include "NodeToolTip.h" #include "NodeView.h" #include "KisPart.h" #include "input/kis_input_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_view_color_scheme.h" #include "kis_icon_utils.h" #include "kis_layer_properties_icons.h" #include "krita_utils.h" #include "kis_config_notifier.h" typedef KisBaseNode::Property* OptionalProperty; #include class NodeDelegate::Private { public: Private() : view(0), edit(0) { } NodeView *view; QPointer edit; NodeToolTip tip; QColor checkersColor1; QColor checkersColor2; QList shiftClickedIndexes; enum StasisOperation { Record, Review, Restore }; QList rightmostProperties(const KisBaseNode::PropertyList &props) const; int numProperties(const QModelIndex &index) const; OptionalProperty findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const; OptionalProperty findVisibilityProperty(KisBaseNode::PropertyList &props) const; void toggleProperty(KisBaseNode::PropertyList &props, OptionalProperty prop, const Qt::KeyboardModifiers modifier, const QModelIndex &index); void togglePropertyRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty, const QList &items, StasisOperation record, bool mode); bool stasisIsDirty(const QModelIndex &root, const OptionalProperty &clickedProperty, bool on = false, bool off = false); void resetPropertyStateRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty); void restorePropertyInStasisRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty); bool checkImmediateStasis(const QModelIndex &root, const OptionalProperty &clickedProperty); void getParentsIndex(QList &items, const QModelIndex &index); void getChildrenIndex(QList &items, const QModelIndex &index); void getSiblingsIndex(QList &items, const QModelIndex &index); }; NodeDelegate::NodeDelegate(NodeView *view, QObject *parent) : QAbstractItemDelegate(parent) , d(new Private) { d->view = view; QApplication::instance()->installEventFilter(this); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(this, SIGNAL(resetVisibilityStasis()), SLOT(slotResetState())); slotConfigChanged(); } NodeDelegate::~NodeDelegate() { delete d; } QSize NodeDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); KisNodeViewColorScheme scm; return QSize(option.rect.width(), scm.rowHeight()); } void NodeDelegate::paint(QPainter *p, const QStyleOptionViewItem &o, const QModelIndex &index) const { p->save(); { QStyleOptionViewItem option = getOptions(o, index); QStyle *style = option.widget ? option.widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, p, option.widget); bool shouldGrayOut = index.data(KisNodeModel::ShouldGrayOutRole).toBool(); if (shouldGrayOut) { option.state &= ~QStyle::State_Enabled; } p->setFont(option.font); drawColorLabel(p, option, index); drawFrame(p, option, index); drawThumbnail(p, option, index); drawText(p, option, index); // BUG: Creating group moves things around (RTL-layout alignment) drawIcons(p, option, index); drawVisibilityIconHijack(p, option, index); // TODO hide when dragging drawDecoration(p, option, index); drawExpandButton(p, option, index); drawBranch(p, option, index); drawProgressBar(p, option, index); } p->restore(); } void NodeDelegate::drawBranch(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { QModelIndex tmp = index.parent(); // there is no indention if we have no parent group, so don't draw a branch if (!tmp.isValid()) return; KisNodeViewColorScheme scm; int rtlNum = (option.direction == Qt::RightToLeft) ? 1 : -1; QRect baseRect = scm.relThumbnailRect(); // Move to current index baseRect.moveTop(option.rect.topLeft().y()); // Move to correct location. if (option.direction == Qt::RightToLeft) { baseRect.moveLeft(option.rect.topRight().x()); } else { baseRect.moveRight(option.rect.topLeft().x()); } QPoint base = baseRect.adjusted(rtlNum*scm.indentation(), 0, rtlNum*scm.indentation(), 0).center() + QPoint(0, scm.iconSize()/4); QPen oldPen = p->pen(); const qreal oldOpacity = p->opacity(); // remember previous opacity p->setOpacity(1.0); QColor color = scm.gridColor(option, d->view); QColor bgColor = option.state & QStyle::State_Selected ? qApp->palette().color(QPalette::Base) : qApp->palette().color(QPalette::Text); color = KritaUtils::blendColors(color, bgColor, 0.9); // TODO: if we are a mask type, use dotted lines for the branch style // p->setPen(QPen(p->pen().color(), 2, Qt::DashLine, Qt::RoundCap, Qt::RoundJoin)); p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); QPoint p2 = base - QPoint(rtlNum*(scm.iconSize()/2), 0); QPoint p3 = base - QPoint(0, scm.iconSize()/2); p->drawLine(base, p2); p->drawLine(base, p3); // draw parent lines (keep drawing until x position is less than 0 QPoint parentBase1 = base + QPoint(rtlNum*scm.indentation(), 0); QPoint parentBase2 = p3 + QPoint(rtlNum*scm.indentation(), 0); // indent lines needs to be very subtle to avoid making the docker busy looking color = KritaUtils::blendColors(color, bgColor, 0.9); // makes it a little lighter than L lines p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); if (tmp.isValid()) { tmp = tmp.parent(); // Ignore the first group as it was already painted } while (tmp.isValid()) { p->drawLine(parentBase1, parentBase2); parentBase1 += QPoint(rtlNum*scm.indentation(), 0); parentBase2 += QPoint(rtlNum*scm.indentation(), 0); tmp = tmp.parent(); } p->setPen(oldPen); p->setOpacity(oldOpacity); } void NodeDelegate::drawColorLabel(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; const int label = index.data(KisNodeModel::ColorLabelIndexRole).toInt(); QColor color = scm.colorLabel(label); if (color.alpha() <= 0) return; QColor bgColor = qApp->palette().color(QPalette::Base); if ((option.state & QStyle::State_MouseOver) && !(option.state & QStyle::State_Selected)) { color = KritaUtils::blendColors(color, bgColor, 0.6); } else { color = KritaUtils::blendColors(color, bgColor, 0.3); } QRect optionRect = option.rect.adjusted(0, 0, scm.indentation(), 0); if (option.state & QStyle::State_Selected) { optionRect = iconsRect(option, index); } if (option.direction == Qt::RightToLeft) { optionRect.moveLeft(option.rect.topLeft().x()); } else { optionRect.moveRight(option.rect.topRight().x()); } p->fillRect(optionRect, color); } void NodeDelegate::drawFrame(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; QPen oldPen = p->pen(); p->setPen(scm.gridColor(option, d->view)); const QRect visibilityRect = visibilityClickRect(option, index); const QRect thumbnailRect = thumbnailClickRect(option, index); const QRect decorationRect = decorationClickRect(option, index); const QRect iconsRectR = iconsRect(option, index); const float topY = thumbnailRect.topLeft().y(); const float bottomY = thumbnailRect.bottomLeft().y(); QPoint bottomLeftPoint; QPoint bottomRightPoint; if (option.direction == Qt::RightToLeft) { bottomLeftPoint = iconsRectR.bottomLeft(); bottomRightPoint = visibilityRect.bottomRight(); } else { bottomLeftPoint = visibilityRect.bottomLeft(); bottomRightPoint = iconsRectR.bottomRight(); } // bottom running horizontal line p->drawLine(bottomLeftPoint.x(), bottomY, bottomRightPoint.x(), bottomY); // visibility icon vertical line - left p->drawLine(visibilityRect.topLeft().x()-1, topY, visibilityRect.bottomLeft().x()-1, bottomY); // visibility icon vertical line - right p->drawLine(visibilityRect.topRight().x()+1, topY, visibilityRect.bottomRight().x()+1, bottomY); // thumbnail vertical line - left p->drawLine(thumbnailRect.topLeft().x(), topY, thumbnailRect.bottomLeft().x(), bottomY); // thumbnail vertical line - right p->drawLine(thumbnailRect.topRight().x(), topY, thumbnailRect.bottomRight().x(), bottomY); // decoration vertical line - left p->drawLine(decorationRect.topLeft().x(), topY, decorationRect.bottomLeft().x(), bottomY); // decoration vertical line - right p->drawLine(decorationRect.topRight().x(), topY, decorationRect.bottomRight().x(), bottomY); // icons' lines are drawn by drawIcons //// For debugging purposes only p->setPen(Qt::blue); //KritaUtils::renderExactRect(p, iconsRectR); //KritaUtils::renderExactRect(p, textRect(option, index)); //KritaUtils::renderExactRect(p, visibilityRect); p->setPen(oldPen); } QRect NodeDelegate::thumbnailClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); KisNodeViewColorScheme scm; QRect rc = scm.relThumbnailRect(); // Move to current index rc.moveTop(option.rect.topLeft().y()); // Move to correct location. if (option.direction == Qt::RightToLeft) { rc.moveLeft(option.rect.topRight().x()); } else { rc.moveRight(option.rect.topLeft().x()); } return rc; } void NodeDelegate::drawThumbnail(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; const int thumbSize = scm.thumbnailSize(); const qreal oldOpacity = p->opacity(); // remember previous opacity QImage img = index.data(int(KisNodeModel::BeginThumbnailRole) + thumbSize).value(); if (!(option.state & QStyle::State_Enabled)) { p->setOpacity(0.35); } // paint in a checkerboard pattern behind the layer contents to represent transparent const int step = scm.thumbnailSize() / 6; QImage checkers(2 * step, 2 * step, QImage::Format_ARGB32); QPainter gc(&checkers); gc.fillRect(QRect(0, 0, step, step), d->checkersColor1); gc.fillRect(QRect(step, 0, step, step), d->checkersColor2); gc.fillRect(QRect(step, step, step, step), d->checkersColor1); gc.fillRect(QRect(0, step, step, step), d->checkersColor2); QRect fitRect = thumbnailClickRect(option, index); // Shrink to icon rect fitRect = kisGrowRect(fitRect, -(scm.thumbnailMargin()+scm.border())); QPoint offset; offset.setX((fitRect.width() - img.width()) / 2); offset.setY((fitRect.height() - img.height()) / 2); offset += fitRect.topLeft(); QBrush brush(checkers); p->setBrushOrigin(offset); p->fillRect(img.rect().translated(offset), brush); p->drawImage(offset, img); p->setOpacity(oldOpacity); // restore old opacity QRect borderRect = kisGrowRect(img.rect(), 1).translated(offset); KritaUtils::renderExactRect(p, borderRect, scm.gridColor(option, d->view)); } QRect NodeDelegate::iconsRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; int propCount = d->numProperties(index); const int iconsWidth = propCount * (scm.iconSize() + 2 * scm.iconMargin()) + (propCount + 1) * scm.border(); QRect fitRect = QRect(0, 0, iconsWidth, scm.rowHeight() - scm.border()); // Move to current index fitRect.moveTop(option.rect.topLeft().y()); // Move to correct location. if (option.direction == Qt::RightToLeft) { fitRect.moveLeft(option.rect.topLeft().x()); } else { fitRect.moveRight(option.rect.topRight().x()); } return fitRect; } QRect NodeDelegate::textRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; static QFont f; static int minbearing = 1337 + 666; //can be 0 or negative, 2003 is less likely if (minbearing == 2003 || f != option.font) { f = option.font; //getting your bearings can be expensive, so we cache them minbearing = option.fontMetrics.minLeftBearing() + option.fontMetrics.minRightBearing(); } const QRect decoRect = decorationClickRect(option, index); const QRect iconRect = iconsRect(option, index); QRect rc = QRect((option.direction == Qt::RightToLeft) ? iconRect.topRight() : decoRect.topRight(), (option.direction == Qt::RightToLeft) ? decoRect.bottomLeft() : iconRect.bottomLeft()); rc.adjust(-(scm.border()+minbearing), 0, (scm.border()+minbearing), 0); return rc; } void NodeDelegate::drawText(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; const QRect rc = textRect(option, index).adjusted(scm.textMargin(), 0, -scm.textMargin(), 0); QPen oldPen = p->pen(); const qreal oldOpacity = p->opacity(); // remember previous opacity p->setPen(option.palette.color(QPalette::Active,QPalette::Text )); if (!(option.state & QStyle::State_Enabled)) { p->setOpacity(0.55); } const QString text = index.data(Qt::DisplayRole).toString(); const QString elided = p->fontMetrics().elidedText(text, Qt::ElideRight, rc.width()); p->drawText(rc, Qt::AlignLeft | Qt::AlignVCenter, elided); p->setPen(oldPen); // restore pen settings p->setOpacity(oldOpacity); } QList NodeDelegate::Private::rightmostProperties(const KisBaseNode::PropertyList &props) const { QList list; QList prependList; list << OptionalProperty(0); list << OptionalProperty(0); list << OptionalProperty(0); KisBaseNode::PropertyList::const_iterator it = props.constBegin(); KisBaseNode::PropertyList::const_iterator end = props.constEnd(); for (; it != end; ++it) { if (!it->isMutable) continue; if (it->id == KisLayerPropertiesIcons::visible.id()) { // noop... } else if (it->id == KisLayerPropertiesIcons::locked.id()) { list[0] = OptionalProperty(&(*it)); } else if (it->id == KisLayerPropertiesIcons::inheritAlpha.id()) { list[1] = OptionalProperty(&(*it)); } else if (it->id == KisLayerPropertiesIcons::alphaLocked.id()) { list[2] = OptionalProperty(&(*it)); } else { prependList.prepend(OptionalProperty(&(*it))); } } { QMutableListIterator i(prependList); i.toBack(); while (i.hasPrevious()) { OptionalProperty val = i.previous(); int emptyIndex = list.lastIndexOf(0); if (emptyIndex < 0) break; list[emptyIndex] = val; i.remove(); } } return prependList + list; } int NodeDelegate::Private::numProperties(const QModelIndex &index) const { KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); QList realProps = rightmostProperties(props); return realProps.size(); } OptionalProperty NodeDelegate::Private::findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const { KisBaseNode::PropertyList::iterator it = props.begin(); KisBaseNode::PropertyList::iterator end = props.end(); for (; it != end; ++it) { if (it->id == refProp->id) { return &(*it); } } return 0; } OptionalProperty NodeDelegate::Private::findVisibilityProperty(KisBaseNode::PropertyList &props) const { KisBaseNode::PropertyList::iterator it = props.begin(); KisBaseNode::PropertyList::iterator end = props.end(); for (; it != end; ++it) { if (it->id == KisLayerPropertiesIcons::visible.id()) { return &(*it); } } return 0; } void NodeDelegate::Private::toggleProperty(KisBaseNode::PropertyList &props, OptionalProperty clickedProperty, const Qt::KeyboardModifiers modifier, const QModelIndex &index) { QModelIndex root(view->rootIndex()); if ((modifier & Qt::ShiftModifier) == Qt::ShiftModifier && clickedProperty->canHaveStasis) { bool mode = true; OptionalProperty prop = findProperty(props, clickedProperty); // XXX: Change to use NodeProperty int position = shiftClickedIndexes.indexOf(index); StasisOperation record = (!prop->isInStasis)? StasisOperation::Record : (position < 0) ? StasisOperation::Review : StasisOperation::Restore; shiftClickedIndexes.clear(); shiftClickedIndexes.push_back(index); QList items; if (modifier == (Qt::ControlModifier | Qt::ShiftModifier)) { mode = false; // inverted mode items.insert(0, index); // important! getSiblingsIndex(items, index); } else { getParentsIndex(items, index); getChildrenIndex(items, index); } togglePropertyRecursive(root, clickedProperty, items, record, mode); } else { // If we have properties in stasis, we need to cancel stasis to avoid overriding // values in stasis. // IMPORTANT -- we also need to check the first row of nodes to determine // if a stasis is currently active in some cases. const bool hasPropInStasis = (shiftClickedIndexes.count() > 0 || checkImmediateStasis(root, clickedProperty)); if (clickedProperty->canHaveStasis && hasPropInStasis) { shiftClickedIndexes.clear(); restorePropertyInStasisRecursive(root, clickedProperty); } else { shiftClickedIndexes.clear(); resetPropertyStateRecursive(root, clickedProperty); clickedProperty->state = !clickedProperty->state.toBool(); clickedProperty->isInStasis = false; view->model()->setData(index, QVariant::fromValue(props), KisNodeModel::PropertiesRole); } } } void NodeDelegate::Private::togglePropertyRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty, const QList &items, StasisOperation record, bool mode) { int rowCount = view->model()->rowCount(root); for (int i = 0; i < rowCount; i++) { QModelIndex idx = view->model()->index(i, 0, root); // The entire property list has to be altered because model->setData cannot set individual properties KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value(); OptionalProperty prop = findProperty(props, clickedProperty); if (!prop) continue; if (record == StasisOperation::Record) { prop->stateInStasis = prop->state.toBool(); } if (record == StasisOperation::Review || record == StasisOperation::Record) { prop->isInStasis = true; if(mode) { //include mode prop->state = (items.contains(idx)) ? QVariant(true) : QVariant(false); } else { // exclude prop->state = (!items.contains(idx))? prop->state : (items.at(0) == idx)? QVariant(true) : QVariant(false); } } else { // restore prop->state = QVariant(prop->stateInStasis); prop->isInStasis = false; } view->model()->setData(idx, QVariant::fromValue(props), KisNodeModel::PropertiesRole); togglePropertyRecursive(idx,clickedProperty, items, record, mode); } } bool NodeDelegate::Private::stasisIsDirty(const QModelIndex &root, const OptionalProperty &clickedProperty, bool on, bool off) { int rowCount = view->model()->rowCount(root); bool result = false; for (int i = 0; i < rowCount; i++) { if (result) break; // return on first find QModelIndex idx = view->model()->index(i, 0, root); // The entire property list has to be altered because model->setData cannot set individual properties KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value(); OptionalProperty prop = findProperty(props, clickedProperty); if (!prop) continue; if (prop->isInStasis) { on = true; } else { off = true; } // stop if both states exist if (on && off) { return true; } result = stasisIsDirty(idx,clickedProperty, on, off); } return result; } void NodeDelegate::Private::resetPropertyStateRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty) { if (!clickedProperty->canHaveStasis) return; int rowCount = view->model()->rowCount(root); for (int i = 0; i < rowCount; i++) { QModelIndex idx = view->model()->index(i, 0, root); // The entire property list has to be altered because model->setData cannot set individual properties KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value(); OptionalProperty prop = findProperty(props, clickedProperty); if (!prop) continue; prop->isInStasis = false; view->model()->setData(idx, QVariant::fromValue(props), KisNodeModel::PropertiesRole); resetPropertyStateRecursive(idx,clickedProperty); } } void NodeDelegate::Private::restorePropertyInStasisRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty) { if (!clickedProperty->canHaveStasis) return; int rowCount = view->model()->rowCount(root); for (int i = 0; i < rowCount; i++) { QModelIndex idx = view->model()->index(i, 0, root); KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value(); OptionalProperty prop = findProperty(props, clickedProperty); if (prop->isInStasis) { prop->isInStasis = false; prop->state = QVariant(prop->stateInStasis); } view->model()->setData(idx, QVariant::fromValue(props), KisNodeModel::PropertiesRole); restorePropertyInStasisRecursive(idx, clickedProperty); } } bool NodeDelegate::Private::checkImmediateStasis(const QModelIndex &root, const OptionalProperty &clickedProperty) { if (!clickedProperty->canHaveStasis) return false; const int rowCount = view->model()->rowCount(root); for (int i = 0; i < rowCount; i++){ QModelIndex idx = view->model()->index(i, 0, root); KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value(); OptionalProperty prop = findProperty(props, clickedProperty); if (prop->isInStasis) { return true; } } return false; } void NodeDelegate::Private::getParentsIndex(QList &items, const QModelIndex &index) { if (!index.isValid()) return; items.append(index); getParentsIndex(items, index.parent()); } void NodeDelegate::Private::getChildrenIndex(QList &items, const QModelIndex &index) { qint32 childs = view->model()->rowCount(index); QModelIndex child; // STEP 1: Go. for (quint16 i = 0; i < childs; ++i) { child = view->model()->index(i, 0, index); items.append(child); getChildrenIndex(items, child); } } void NodeDelegate::Private::getSiblingsIndex(QList &items, const QModelIndex &index) { qint32 numberOfLeaves = view->model()->rowCount(index.parent()); QModelIndex item; // STEP 1: Go. for (quint16 i = 0; i < numberOfLeaves; ++i) { item = view->model()->index(i, 0, index.parent()); if (item != index) { items.append(item); } } } void NodeDelegate::drawIcons(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; const QRect rc = iconsRect(option, index); QTransform oldTransform = p->transform(); QPen oldPen = p->pen(); p->setTransform(QTransform::fromTranslate(rc.x(), rc.y())); p->setPen(scm.gridColor(option, d->view)); int x = 0; const int y = (scm.rowHeight() - scm.border() - scm.iconSize()) / 2; KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); QList realProps = d->rightmostProperties(props); if (option.direction == Qt::RightToLeft) { std::reverse(realProps.begin(), realProps.end()); } Q_FOREACH (OptionalProperty prop, realProps) { if (option.direction == Qt::LeftToRight) p->drawLine(x, 0, x, scm.rowHeight() - scm.border()); x += scm.iconMargin(); if (prop) { QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon; bool fullColor = prop->state.toBool() && option.state & QStyle::State_Enabled; const qreal oldOpacity = p->opacity(); // remember previous opacity if (fullColor) { p->setOpacity(1.0); } else { p->setOpacity(0.35); } p->drawPixmap(x, y, icon.pixmap(scm.iconSize(), QIcon::Normal)); p->setOpacity(oldOpacity); // restore old opacity } x += scm.iconSize() + scm.iconMargin(); if (!(option.direction == Qt::LeftToRight)) p->drawLine(x, 0, x, scm.rowHeight() - scm.border()); x += scm.border(); } p->setTransform(oldTransform); p->setPen(oldPen); } QRect NodeDelegate::visibilityClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); KisNodeViewColorScheme scm; QRect rc = scm.relVisibilityRect(); rc.setHeight(scm.rowHeight()); // Move to current index rc.moveCenter(option.rect.center()); // Move to correct location. if (option.direction == Qt::RightToLeft) { // HACK: Without the -5, the right edge is outside the view rc.moveRight(d->view->width()-5); } else { rc.moveLeft(0); } return rc; } QRect NodeDelegate::decorationClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); KisNodeViewColorScheme scm; QRect rc = scm.relDecorationRect(); // Move to current index rc.moveTop(option.rect.topLeft().y()); rc.setHeight(scm.rowHeight()); // Move to correct location. if (option.direction == Qt::RightToLeft) { rc.moveRight(option.rect.topRight().x()); } else { rc.moveLeft(option.rect.topLeft().x()); } return rc; } void NodeDelegate::drawVisibilityIconHijack(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { /** * Small hack Alert: * * Here wepaint over the area that sits basically outside our layer's * row. Anyway, just update it later... */ KisNodeViewColorScheme scm; KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); OptionalProperty prop = d->findVisibilityProperty(props); if (!prop) return; QRect fitRect = visibilityClickRect(option, index); // Shrink to icon rect fitRect = kisGrowRect(fitRect, -(scm.visibilityMargin()+scm.border())); QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon; // if we are not showing the layer, make the icon slightly transparent like other inactive icons const qreal oldOpacity = p->opacity(); if (!prop->state.toBool()) { p->setOpacity(0.35); } QPixmap pixmapIcon(icon.pixmap(scm.visibilitySize(), QIcon::Active)); p->drawPixmap(fitRect.x(), fitRect.center().y() - scm.visibilitySize() / 2, pixmapIcon); if (prop->isInStasis) { QPainter::CompositionMode prevComposition = p->compositionMode(); p->setCompositionMode(QPainter::CompositionMode_HardLight); pixmapIcon = icon.pixmap(scm.visibilitySize(), QIcon::Active); QBitmap mask = pixmapIcon.mask(); pixmapIcon.fill(d->view->palette().color(QPalette::Highlight)); pixmapIcon.setMask(mask); p->drawPixmap(fitRect.x(), fitRect.center().y() - scm.visibilitySize() / 2, pixmapIcon); p->setCompositionMode(prevComposition); } p->setOpacity(oldOpacity); //// For debugging purposes only // // // p->save(); // // // p->setPen(Qt::blue); // // // KritaUtils::renderExactRect(p, visibilityClickRect(option, index)); // // // p->restore(); } void NodeDelegate::drawDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; QIcon icon = index.data(Qt::DecorationRole).value(); if (!icon.isNull()) { QPixmap pixmap = icon.pixmap(scm.decorationSize(), (option.state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled); QRect rc = decorationClickRect(option, index); // Shrink to icon rect rc = kisGrowRect(rc, -(scm.decorationMargin()+scm.border())); const qreal oldOpacity = p->opacity(); // remember previous opacity if (!(option.state & QStyle::State_Enabled)) { p->setOpacity(0.35); } p->drawPixmap(rc.topLeft()-QPoint(0, 1), pixmap); p->setOpacity(oldOpacity); // restore old opacity } } void NodeDelegate::drawExpandButton(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); KisNodeViewColorScheme scm; QRect rc = decorationClickRect(option, index); // Move to current index // rc.moveTop(option.rect.topLeft().y()); // Shrink to icon rect rc = kisGrowRect(rc, -(scm.decorationMargin()+scm.border())); if (!(option.state & QStyle::State_Children)) return; QString iconName = option.state & QStyle::State_Open ? "arrow-down" : ((option.direction == Qt::RightToLeft) ? "arrow-left" : "arrow-right"); QIcon icon = KisIconUtils::loadIcon(iconName); QPixmap pixmap = icon.pixmap(rc.width(), (option.state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled); p->drawPixmap(rc.bottomLeft()-QPoint(0, scm.decorationSize()-1), pixmap); } bool NodeDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { KisNodeViewColorScheme scm; QStyleOptionViewItem newOption = option; newOption.rect = d->view->originalVisualRect(index); if ((event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) && (index.flags() & Qt::ItemIsEnabled)) { QMouseEvent *mouseEvent = static_cast(event); /** * Small hack Alert: * * Here we handle clicking even when it happened outside * the rectangle of the current index. The point is, we * use some virtual scroling offset to move the tree to the * right of the visibility icon. So the icon itself is placed * in an empty area that doesn't belong to any index. But we still * handle it. */ const QRect visibilityRect = visibilityClickRect(newOption, index); const bool visibilityClicked = visibilityRect.isValid() && visibilityRect.contains(mouseEvent->pos()); const QRect thumbnailRect = thumbnailClickRect(newOption, index); const bool thumbnailClicked = thumbnailRect.isValid() && thumbnailRect.contains(mouseEvent->pos()); const QRect decorationRect = decorationClickRect(newOption, index); const bool decorationClicked = decorationRect.isValid() && decorationRect.contains(mouseEvent->pos()); const QRect iconsRect = this->iconsRect(newOption, index); const bool iconsClicked = iconsRect.isValid() && iconsRect.contains(mouseEvent->pos()); const bool leftButton = mouseEvent->buttons() & Qt::LeftButton; if (leftButton && iconsClicked) { KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); QList realProps = d->rightmostProperties(props); if (newOption.direction == Qt::RightToLeft) { std::reverse(realProps.begin(), realProps.end()); } const int numProps = realProps.size(); const int iconWidth = scm.iconSize() + 2 * scm.iconMargin() + scm.border(); const int xPos = mouseEvent->pos().x() - iconsRect.left(); const int clickedIcon = xPos / iconWidth; const int distToBorder = qMin(xPos % iconWidth, iconWidth - xPos % iconWidth); if (iconsClicked && clickedIcon >= 0 && clickedIcon < numProps && distToBorder > scm.iconMargin()) { OptionalProperty clickedProperty = realProps[clickedIcon]; if (!clickedProperty) return false; d->toggleProperty(props, clickedProperty, mouseEvent->modifiers(), index); return true; } } else if (leftButton && visibilityClicked) { KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); OptionalProperty clickedProperty = d->findVisibilityProperty(props); if (!clickedProperty) return false; d->toggleProperty(props, clickedProperty, mouseEvent->modifiers(), index); return true; } else if (leftButton && decorationClicked) { bool isExpandable = model->hasChildren(index); if (isExpandable) { bool isExpanded = d->view->isExpanded(index); d->view->setExpanded(index, !isExpanded); } return true; } else if (leftButton && thumbnailClicked) { bool hasCorrectModifier = false; SelectionAction action = SELECTION_REPLACE; if (mouseEvent->modifiers() == Qt::ControlModifier) { action = SELECTION_REPLACE; hasCorrectModifier = true; } else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { action = SELECTION_ADD; hasCorrectModifier = true; } else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::AltModifier)) { action = SELECTION_SUBTRACT; hasCorrectModifier = true; } else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) { action = SELECTION_INTERSECT; hasCorrectModifier = true; } if (hasCorrectModifier) { model->setData(index, QVariant(int(action)), KisNodeModel::SelectOpaqueRole); } d->view->setCurrentIndex(index); return hasCorrectModifier; //If not here then the item is !expanded when reaching return false; } if (mouseEvent->button() == Qt::LeftButton && mouseEvent->modifiers() == Qt::AltModifier) { d->view->setCurrentIndex(index); model->setData(index, true, KisNodeModel::AlternateActiveRole); return true; } } else if (event->type() == QEvent::ToolTip) { if (!KisConfig(true).hidePopups()) { QHelpEvent *helpEvent = static_cast(event); d->tip.showTip(d->view, helpEvent->pos(), newOption, index); } return true; } else if (event->type() == QEvent::Leave) { d->tip.hide(); } return false; } QWidget *NodeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex &index) const { // #400357 do not override QAbstractItemDelegate::setEditorData to update editor's text // because replacing the text while user type is confusing const QString &text = index.data(Qt::DisplayRole).toString(); d->edit = new QLineEdit(text, parent); d->edit->setFocusPolicy(Qt::StrongFocus); d->edit->installEventFilter(const_cast(this)); //hack? return d->edit; } void NodeDelegate::setModelData(QWidget *widget, QAbstractItemModel *model, const QModelIndex &index) const { QLineEdit *edit = qobject_cast(widget); Q_ASSERT(edit); model->setData(index, edit->text(), Qt::DisplayRole); } void NodeDelegate::updateEditorGeometry(QWidget *widget, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); widget->setGeometry(option.rect); } +void NodeDelegate::toggleSolo(const QModelIndex &index) { + KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); + OptionalProperty visibilityProperty = d->findVisibilityProperty(props); + d->toggleProperty(props, visibilityProperty, Qt::ShiftModifier, index); +} + // PROTECTED bool NodeDelegate::eventFilter(QObject *object, QEvent *event) { switch (event->type()) { case QEvent::MouseButtonPress: { if (d->edit) { QMouseEvent *me = static_cast(event); if (!QRect(d->edit->mapToGlobal(QPoint()), d->edit->size()).contains(me->globalPos())) { emit commitData(d->edit); emit closeEditor(d->edit); } } } break; case QEvent::KeyPress: { QLineEdit *edit = qobject_cast(object); if (edit && edit == d->edit) { QKeyEvent *ke = static_cast(event); switch (ke->key()) { case Qt::Key_Escape: emit closeEditor(edit); return true; case Qt::Key_Tab: emit commitData(edit); emit closeEditor(edit, EditNextItem); return true; case Qt::Key_Backtab: emit commitData(edit); emit closeEditor(edit, EditPreviousItem); return true; case Qt::Key_Return: case Qt::Key_Enter: emit commitData(edit); emit closeEditor(edit); return true; default: break; } } } break; case QEvent::ShortcutOverride : { QLineEdit *edit = qobject_cast(object); if (edit && edit == d->edit){ auto* key = static_cast(event); if (key->modifiers() == Qt::NoModifier){ switch (key->key()){ case Qt::Key_Escape: case Qt::Key_Tab: case Qt::Key_Backtab: case Qt::Key_Return: case Qt::Key_Enter: event->accept(); return true; default: break; } } } } break; case QEvent::FocusOut : { QLineEdit *edit = qobject_cast(object); if (edit && edit == d->edit) { emit commitData(edit); emit closeEditor(edit); } } default: break; } return QAbstractItemDelegate::eventFilter(object, event); } // PRIVATE QStyleOptionViewItem NodeDelegate::getOptions(const QStyleOptionViewItem &o, const QModelIndex &index) { QStyleOptionViewItem option = o; QVariant v = index.data(Qt::FontRole); if (v.isValid()) { option.font = v.value(); option.fontMetrics = QFontMetrics(option.font); } v = index.data(Qt::TextAlignmentRole); if (v.isValid()) option.displayAlignment = QFlag(v.toInt()); v = index.data(Qt::TextColorRole); if (v.isValid()) option.palette.setColor(QPalette::Text, v.value()); v = index.data(Qt::BackgroundColorRole); if (v.isValid()) option.palette.setColor(QPalette::Window, v.value()); return option; } void NodeDelegate::drawProgressBar(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { QVariant value = index.data(KisNodeModel::ProgressRole); if (!value.isNull() && (value.toInt() >= 0 && value.toInt() <= 100)) { /// The progress bar will display under the layer name area. The bars have accurate data, so we /// probably don't need to also show the actual number for % complete KisNodeViewColorScheme scm; const QRect thumbnailRect = thumbnailClickRect(option, index); const QRect iconsRectR = iconsRect(option, index); const int height = 5; const QRect rc = QRect( ((option.direction == Qt::RightToLeft) ? iconsRectR.bottomRight() : thumbnailRect.bottomRight()) - QPoint(0, height), ((option.direction == Qt::RightToLeft) ? thumbnailRect.bottomLeft() : iconsRectR.bottomLeft())); p->save(); { p->setClipRect(rc); QStyle* style = QApplication::style(); QStyleOptionProgressBar opt; opt.rect = rc; opt.minimum = 0; opt.maximum = 100; opt.progress = value.toInt(); opt.textVisible = false; opt.textAlignment = Qt::AlignHCenter; opt.text = i18n("%1 %", opt.progress); opt.orientation = Qt::Horizontal; opt.state = option.state; style->drawControl(QStyle::CE_ProgressBar, &opt, p, 0); } p->restore(); } } void NodeDelegate::slotConfigChanged() { KisConfig cfg(true); d->checkersColor1 = cfg.checkersColor1(); d->checkersColor2 = cfg.checkersColor2(); } void NodeDelegate::slotUpdateIcon() { KisLayerPropertiesIcons::instance()->updateIcons(); } void NodeDelegate::slotResetState(){ NodeView *view = d->view; QModelIndex root = view->rootIndex(); int childs = view->model()->rowCount(root); if (childs > 0){ QModelIndex firstChild = view->model()->index(0, 0, root); KisBaseNode::PropertyList props = firstChild.data(KisNodeModel::PropertiesRole).value(); OptionalProperty visibilityProperty = d->findVisibilityProperty(props); if(d->stasisIsDirty(root, visibilityProperty)){ // clean inStasis if mixed! d->resetPropertyStateRecursive(root, visibilityProperty); } } } diff --git a/plugins/dockers/layerdocker/NodeDelegate.h b/plugins/dockers/layerdocker/NodeDelegate.h index 65771ad04a..1c6393a1da 100644 --- a/plugins/dockers/layerdocker/NodeDelegate.h +++ b/plugins/dockers/layerdocker/NodeDelegate.h @@ -1,92 +1,94 @@ /* Copyright (c) 2006 Gábor Lehel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_DOCUMENT_SECTION_DELEGATE_H #define KIS_DOCUMENT_SECTION_DELEGATE_H #include class NodeView; class KisNodeModel; /** * See KisNodeModel and NodeView. * * A delegate provides the gui machinery, using Qt's model/view terminology. * This class is owned by NodeView to do the work of generating the * graphical representation of each item. */ class NodeDelegate: public QAbstractItemDelegate { Q_OBJECT public: explicit NodeDelegate(NodeView *view, QObject *parent = 0); ~NodeDelegate() override; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex& index) const override; + void toggleSolo(const QModelIndex &index); + void slotUpdateIcon(); Q_SIGNALS: void resetVisibilityStasis(); protected: bool eventFilter(QObject *object, QEvent *event) override; private: typedef KisNodeModel Model; typedef NodeView View; class Private; Private* const d; static QStyleOptionViewItem getOptions(const QStyleOptionViewItem &option, const QModelIndex &index); void drawProgressBar(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawBranch(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawColorLabel(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawFrame(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; QRect thumbnailClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawThumbnail(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; QRect iconsRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; QRect textRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawText(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawIcons(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; QRect visibilityClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawVisibilityIconHijack(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; QRect decorationClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawExpandButton(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; private Q_SLOTS: void slotConfigChanged(); void slotResetState(); }; #endif diff --git a/plugins/dockers/layerdocker/NodeView.cpp b/plugins/dockers/layerdocker/NodeView.cpp index 057a9676af..29e0818b15 100644 --- a/plugins/dockers/layerdocker/NodeView.cpp +++ b/plugins/dockers/layerdocker/NodeView.cpp @@ -1,592 +1,596 @@ /* Copyright (c) 2006 Gábor Lehel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "NodeView.h" #include "NodePropertyAction_p.h" #include "NodeDelegate.h" #include "NodeViewVisibilityDelegate.h" #include "kis_node_model.h" #include "kis_signals_blocker.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_view_color_scheme.h" #ifdef HAVE_X11 #define DRAG_WHILE_DRAG_WORKAROUND #endif #ifdef DRAG_WHILE_DRAG_WORKAROUND #define DRAG_WHILE_DRAG_WORKAROUND_START() d->isDragging = true #define DRAG_WHILE_DRAG_WORKAROUND_STOP() d->isDragging = false #else #define DRAG_WHILE_DRAG_WORKAROUND_START() #define DRAG_WHILE_DRAG_WORKAROUND_STOP() #endif class Q_DECL_HIDDEN NodeView::Private { public: Private(NodeView* _q) : delegate(_q, _q) , mode(DetailedMode) #ifdef DRAG_WHILE_DRAG_WORKAROUND , isDragging(false) #endif { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("NodeView"); mode = (DisplayMode) group.readEntry("NodeViewMode", (int)MinimalMode); } NodeDelegate delegate; DisplayMode mode; QPersistentModelIndex hovered; QPoint lastPos; #ifdef DRAG_WHILE_DRAG_WORKAROUND bool isDragging; #endif }; NodeView::NodeView(QWidget *parent) : QTreeView(parent) , m_draggingFlag(false) , d(new Private(this)) { setItemDelegateForColumn(0, &d->delegate); setMouseTracking(true); setSelectionBehavior(SelectRows); setDefaultDropAction(Qt::MoveAction); setVerticalScrollMode(QAbstractItemView::ScrollPerItem); setSelectionMode(QAbstractItemView::ExtendedSelection); header()->hide(); setDragEnabled(true); setDragDropMode(QAbstractItemView::DragDrop); setAcceptDrops(true); setDropIndicatorShown(true); { QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this); if (scroller) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } } } NodeView::~NodeView() { delete d; } void NodeView::setDisplayMode(DisplayMode mode) { if (d->mode != mode) { d->mode = mode; KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("NodeView"); group.writeEntry("NodeViewMode", (int)mode); scheduleDelayedItemsLayout(); } } NodeView::DisplayMode NodeView::displayMode() const { return d->mode; } void NodeView::addPropertyActions(QMenu *menu, const QModelIndex &index) { KisBaseNode::PropertyList list = index.data(KisNodeModel::PropertiesRole).value(); for (int i = 0, n = list.count(); i < n; ++i) { if (list.at(i).isMutable) { PropertyAction *a = new PropertyAction(i, list.at(i), index, menu); connect(a, SIGNAL(toggled(bool,QPersistentModelIndex,int)), this, SLOT(slotActionToggled(bool,QPersistentModelIndex,int))); menu->addAction(a); } } } void NodeView::updateNode(const QModelIndex &index) { dataChanged(index, index); } +void NodeView::toggleSolo(const QModelIndex &index) { + d->delegate.toggleSolo(index); +} + QItemSelectionModel::SelectionFlags NodeView::selectionCommand(const QModelIndex &index, const QEvent *event) const { /** * Qt has a bug: when we Ctrl+click on an item, the item's * selections gets toggled on mouse *press*, whereas usually it is * done on mouse *release*. Therefore the user cannot do a * Ctrl+D&D with the default configuration. This code fixes the * problem by manually returning QItemSelectionModel::NoUpdate * flag when the user clicks on an item and returning * QItemSelectionModel::Toggle on release. */ if (event && (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) && index.isValid()) { const QMouseEvent *mevent = static_cast(event); if (mevent->button() == Qt::RightButton && selectionModel()->selectedIndexes().contains(index)) { // Allow calling context menu for multiple layers return QItemSelectionModel::NoUpdate; } if (event->type() == QEvent::MouseButtonPress && (mevent->modifiers() & Qt::ControlModifier)) { return QItemSelectionModel::NoUpdate; } if (event->type() == QEvent::MouseButtonRelease && (mevent->modifiers() & Qt::ControlModifier)) { return QItemSelectionModel::Toggle; } } /** * Qt 5.6 has a bug: it reads global modifiers, not the ones * passed from event. So if you paste an item using Ctrl+V it'll * select multiple layers for you */ Qt::KeyboardModifiers globalModifiers = QApplication::keyboardModifiers(); if (!event && globalModifiers != Qt::NoModifier) { return QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows; } return QAbstractItemView::selectionCommand(index, event); } QRect NodeView::visualRect(const QModelIndex &index) const { QRect rc = QTreeView::visualRect(index); if (layoutDirection() == Qt::RightToLeft) rc.setRight(width()); else rc.setLeft(0); return rc; } QRect NodeView::originalVisualRect(const QModelIndex &index) const { return QTreeView::visualRect(index); } QModelIndex NodeView::indexAt(const QPoint &point) const { KisNodeViewColorScheme scm; QModelIndex index = QTreeView::indexAt(point); if (!index.isValid()) { // Middle is a good position for both LTR and RTL layouts // First reset x, then get the x in the middle index = QTreeView::indexAt(point - QPoint(point.x(), 0) + QPoint(width() / 2, 0)); } return index; } bool NodeView::viewportEvent(QEvent *e) { if (model()) { switch(e->type()) { case QEvent::MouseButtonPress: { DRAG_WHILE_DRAG_WORKAROUND_STOP(); const QPoint pos = static_cast(e)->pos(); d->lastPos = pos; if (!indexAt(pos).isValid()) { return QTreeView::viewportEvent(e); } QModelIndex index = model()->buddy(indexAt(pos)); if (d->delegate.editorEvent(e, model(), optionForIndex(index), index)) { return true; } } break; case QEvent::Leave: { QEvent e(QEvent::Leave); d->delegate.editorEvent(&e, model(), optionForIndex(d->hovered), d->hovered); d->hovered = QModelIndex(); } break; case QEvent::MouseMove: { #ifdef DRAG_WHILE_DRAG_WORKAROUND if (d->isDragging) { return false; } #endif const QPoint pos = static_cast(e)->pos(); QModelIndex hovered = indexAt(pos); if (hovered != d->hovered) { if (d->hovered.isValid()) { QEvent e(QEvent::Leave); d->delegate.editorEvent(&e, model(), optionForIndex(d->hovered), d->hovered); } if (hovered.isValid()) { QEvent e(QEvent::Enter); d->delegate.editorEvent(&e, model(), optionForIndex(hovered), hovered); } d->hovered = hovered; } /* This is a workaround for a bug in QTreeView that immediately begins a dragging action when the mouse lands on the decoration/icon of a different index and moves 1 pixel or more */ Qt::MouseButtons buttons = static_cast(e)->buttons(); if ((Qt::LeftButton | Qt::MidButton) & buttons) { if ((pos - d->lastPos).manhattanLength() > qApp->startDragDistance()) { return QTreeView::viewportEvent(e); } return true; } } break; case QEvent::ToolTip: { const QPoint pos = static_cast(e)->pos(); if (!indexAt(pos).isValid()) { return QTreeView::viewportEvent(e); } QModelIndex index = model()->buddy(indexAt(pos)); return d->delegate.editorEvent(e, model(), optionForIndex(index), index); } break; case QEvent::Resize: { scheduleDelayedItemsLayout(); break; } default: break; } } return QTreeView::viewportEvent(e); } void NodeView::contextMenuEvent(QContextMenuEvent *e) { QTreeView::contextMenuEvent(e); QModelIndex i = indexAt(e->pos()); if (model()) i = model()->buddy(i); showContextMenu(e->globalPos(), i); } void NodeView::showContextMenu(const QPoint &globalPos, const QModelIndex &index) { emit contextMenuRequested(globalPos, index); } void NodeView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { QTreeView::currentChanged(current, previous); if (current != previous) { Q_ASSERT(!current.isValid() || current.model() == model()); model()->setData(current, true, KisNodeModel::ActiveRole); } } void NodeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &/*roles*/) { QTreeView::dataChanged(topLeft, bottomRight); for (int x = topLeft.row(); x <= bottomRight.row(); ++x) { for (int y = topLeft.column(); y <= bottomRight.column(); ++y) { QModelIndex index = topLeft.sibling(x, y); if (index.data(KisNodeModel::ActiveRole).toBool()) { if (currentIndex() != index) { setCurrentIndex(index); } return; } } } } void NodeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QTreeView::selectionChanged(selected, deselected); emit selectionChanged(selectedIndexes()); } void NodeView::slotActionToggled(bool on, const QPersistentModelIndex &index, int num) { KisBaseNode::PropertyList list = index.data(KisNodeModel::PropertiesRole).value(); list[num].state = on; const_cast(index.model())->setData(index, QVariant::fromValue(list), KisNodeModel::PropertiesRole); } QStyleOptionViewItem NodeView::optionForIndex(const QModelIndex &index) const { QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); if (index == currentIndex()) option.state |= QStyle::State_HasFocus; return option; } void NodeView::startDrag(Qt::DropActions supportedActions) { DRAG_WHILE_DRAG_WORKAROUND_START(); if (displayMode() == NodeView::ThumbnailMode) { const QModelIndexList indexes = selectionModel()->selectedIndexes(); if (!indexes.isEmpty()) { QMimeData *data = model()->mimeData(indexes); if (!data) { return; } QDrag *drag = new QDrag(this); drag->setPixmap(createDragPixmap()); drag->setMimeData(data); //m_dragSource = this; drag->exec(supportedActions); } } else { QTreeView::startDrag(supportedActions); } } QPixmap NodeView::createDragPixmap() const { const QModelIndexList selectedIndexes = selectionModel()->selectedIndexes(); Q_ASSERT(!selectedIndexes.isEmpty()); const int itemCount = selectedIndexes.count(); // If more than one item is dragged, align the items inside a // rectangular grid. The maximum grid size is limited to 4 x 4 items. int xCount = 2; int size = 96; if (itemCount > 9) { xCount = 4; size = KisIconUtils::SizeLarge; } else if (itemCount > 4) { xCount = 3; size = KisIconUtils::SizeHuge; } else if (itemCount < xCount) { xCount = itemCount; } int yCount = itemCount / xCount; if (itemCount % xCount != 0) { ++yCount; } if (yCount > xCount) { yCount = xCount; } // Draw the selected items into the grid cells QPixmap dragPixmap(xCount * size + xCount - 1, yCount * size + yCount - 1); dragPixmap.fill(Qt::transparent); QPainter painter(&dragPixmap); int x = 0; int y = 0; Q_FOREACH (const QModelIndex &selectedIndex, selectedIndexes) { const QImage img = selectedIndex.data(int(KisNodeModel::BeginThumbnailRole) + size).value(); painter.drawPixmap(x, y, QPixmap().fromImage(img.scaled(QSize(size, size), Qt::KeepAspectRatio, Qt::SmoothTransformation))); x += size + 1; if (x >= dragPixmap.width()) { x = 0; y += size + 1; } if (y >= dragPixmap.height()) { break; } } return dragPixmap; } void NodeView::resizeEvent(QResizeEvent * event) { KisNodeViewColorScheme scm; header()->setStretchLastSection(false); header()->setOffset(-scm.visibilityColumnWidth()); header()->resizeSection(0, event->size().width() - scm.visibilityColumnWidth()); setIndentation(scm.indentation()); QTreeView::resizeEvent(event); } void NodeView::paintEvent(QPaintEvent *event) { event->accept(); QTreeView::paintEvent(event); // Paint the line where the slide should go if (isDragging() && (displayMode() == NodeView::ThumbnailMode)) { QSize size(visualRect(model()->index(0, 0, QModelIndex())).width(), visualRect(model()->index(0, 0, QModelIndex())).height()); int numberRow = cursorPageIndex(); int scrollBarValue = verticalScrollBar()->value(); QPoint point1(0, numberRow * size.height() - scrollBarValue); QPoint point2(size.width(), numberRow * size.height() - scrollBarValue); QLineF line(point1, point2); QPainter painter(this->viewport()); QPen pen = QPen(palette().brush(QPalette::Highlight), 8); pen.setCapStyle(Qt::RoundCap); painter.setPen(pen); painter.setOpacity(0.8); painter.drawLine(line); } } void NodeView::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const { Q_UNUSED(painter); Q_UNUSED(rect); Q_UNUSED(index); /** * Noop... Everything is going to be painted by NodeDelegate. * So this override basically disables painting of Qt's branch-lines. */ } void NodeView::dropEvent(QDropEvent *ev) { if (displayMode() == NodeView::ThumbnailMode) { setDraggingFlag(false); ev->accept(); clearSelection(); if (!model()) { return; } int newIndex = cursorPageIndex(); model()->dropMimeData(ev->mimeData(), ev->dropAction(), newIndex, -1, QModelIndex()); return; } QTreeView::dropEvent(ev); DRAG_WHILE_DRAG_WORKAROUND_STOP(); } int NodeView::cursorPageIndex() const { QSize size(visualRect(model()->index(0, 0, QModelIndex())).width(), visualRect(model()->index(0, 0, QModelIndex())).height()); int scrollBarValue = verticalScrollBar()->value(); QPoint cursorPosition = QWidget::mapFromGlobal(QCursor::pos()); int numberRow = (cursorPosition.y() + scrollBarValue) / size.height(); //If cursor is at the half button of the page then the move action is performed after the slide, otherwise it is //performed before the page if (abs((cursorPosition.y() + scrollBarValue) - size.height()*numberRow) > (size.height()/2)) { numberRow++; } if (numberRow > model()->rowCount(QModelIndex())) { numberRow = model()->rowCount(QModelIndex()); } return numberRow; } void NodeView::dragEnterEvent(QDragEnterEvent *ev) { DRAG_WHILE_DRAG_WORKAROUND_START(); QVariant data = QVariant::fromValue( static_cast(const_cast(ev->mimeData()))); model()->setData(QModelIndex(), data, KisNodeModel::DropEnabled); QTreeView::dragEnterEvent(ev); } void NodeView::dragMoveEvent(QDragMoveEvent *ev) { DRAG_WHILE_DRAG_WORKAROUND_START(); if (displayMode() == NodeView::ThumbnailMode) { ev->accept(); if (!model()) { return; } QTreeView::dragMoveEvent(ev); setDraggingFlag(); viewport()->update(); return; } QTreeView::dragMoveEvent(ev); } void NodeView::dragLeaveEvent(QDragLeaveEvent *e) { if (displayMode() == NodeView::ThumbnailMode) { setDraggingFlag(false); } else { QTreeView::dragLeaveEvent(e); } DRAG_WHILE_DRAG_WORKAROUND_STOP(); } bool NodeView::isDragging() const { return m_draggingFlag; } void NodeView::setDraggingFlag(bool flag) { m_draggingFlag = flag; } void NodeView::slotUpdateIcons() { d->delegate.slotUpdateIcon(); } void NodeView::slotScrollerStateChanged(QScroller::State state){ KisKineticScroller::updateCursor(this, state); } diff --git a/plugins/dockers/layerdocker/NodeView.h b/plugins/dockers/layerdocker/NodeView.h index 489c2c1c85..9a2c5e9300 100644 --- a/plugins/dockers/layerdocker/NodeView.h +++ b/plugins/dockers/layerdocker/NodeView.h @@ -1,187 +1,189 @@ /* Copyright (c) 2006 Gábor Lehel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_DOCUMENT_SECTION_VIEW_H #define KIS_DOCUMENT_SECTION_VIEW_H #include #include class QStyleOptionViewItem; class KisNodeModel; /** * A widget displaying the Krita nodes (layers, masks, local selections, etc.) * * The widget can show the document sections as big thumbnails, * in a listview with two rows of informative text and icons, * or as single rows of text and property icons. * * This class is designed as a Qt model-view widget. * * The Qt documentation explains the design and terminology for these classes: * https://doc.qt.io/qt-5/model-view-programming.html * * This widget should work correctly in your Qt designer .ui file. */ class NodeView: public QTreeView { Q_OBJECT Q_SIGNALS: /** * Emitted whenever the user clicks with the secondary mouse * button on an item. It is up to the application to design the * contents of the context menu and show it. */ void contextMenuRequested(const QPoint &globalPos, const QModelIndex &index); void selectionChanged(const QModelIndexList &); public: /** * Create a new NodeView. */ explicit NodeView(QWidget *parent = 0); ~NodeView() override; /// how items should be displayed enum DisplayMode { /// large fit-to-width thumbnails, with only titles or page numbers ThumbnailMode, /// smaller thumbnails, with titles and property icons in two rows DetailedMode, /// no thumbnails, with titles and property icons in a single row MinimalMode }; void resizeEvent(QResizeEvent * event) override; void paintEvent (QPaintEvent *event) override; void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override; void dropEvent(QDropEvent *ev) override; void dragEnterEvent(QDragEnterEvent *e) override; void dragMoveEvent(QDragMoveEvent *ev) override; void dragLeaveEvent(QDragLeaveEvent *e) override; /** * Set the display mode of the view to one of the options. * * @param mode The NodeView::DisplayMode mode */ void setDisplayMode(DisplayMode mode); /** * @return the currently active display mode */ DisplayMode displayMode() const; /** * Add toggle actions for all the properties associated with the * current document section associated with the model index to the * specified menu. * * For instance, if a document section can be locked and visible, * the menu will be expanded with locked and visible toggle * actions. * * For instance @code NodeView * nodeView; QModelIndex index = getCurrentNode(); QMenu menu; if (index.isValid()) { sectionView->addPropertyActions(&menu, index); } else { menu.addAction(...); // Something to create a new document section, for example. } @endcode * * @param menu A pointer to the menu that will be expanded with * the toglge actions * @param index The model index associated with the document * section that may or may not provide a number of toggle actions. */ void addPropertyActions(QMenu *menu, const QModelIndex &index); void updateNode(const QModelIndex &index); + void toggleSolo(const QModelIndex &index); + QRect originalVisualRect(const QModelIndex &index) const; protected: QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &index, const QEvent *event) const override; QRect visualRect(const QModelIndex &index) const override; QModelIndex indexAt(const QPoint &point) const override; bool viewportEvent(QEvent *event) override; void contextMenuEvent(QContextMenuEvent *event) override; virtual void showContextMenu(const QPoint &globalPos, const QModelIndex &index); void startDrag (Qt::DropActions supportedActions) override; QPixmap createDragPixmap() const; /** * Calculates the index of the nearest item to the cursor position */ int cursorPageIndex() const; public Q_SLOTS: /// called with a theme change to refresh icon colors void slotUpdateIcons(); void slotScrollerStateChanged(QScroller::State state); protected Q_SLOTS: void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override; void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) override; void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override; private Q_SLOTS: void slotActionToggled(bool on, const QPersistentModelIndex &index, int property); private: /** * Permit to know if a slide is dragging * * @return boolean */ bool isDragging() const; /** * Setter for the dragging flag * * @param flag boolean */ void setDraggingFlag(bool flag = true); bool m_draggingFlag; QStyleOptionViewItem optionForIndex(const QModelIndex &index) const; typedef KisNodeModel Model; class PropertyAction; class Private; Private* const d; }; #endif diff --git a/plugins/dockers/palettedocker/palettedocker_dock.cpp b/plugins/dockers/palettedocker/palettedocker_dock.cpp index 689d76c0a5..d507a5d51a 100644 --- a/plugins/dockers/palettedocker/palettedocker_dock.cpp +++ b/plugins/dockers/palettedocker/palettedocker_dock.cpp @@ -1,412 +1,415 @@ /* * Copyright (c) 2013 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "palettedocker_dock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgpalettedock.h" PaletteDockerDock::PaletteDockerDock( ) : QDockWidget(i18n("Palette")) , m_ui(new Ui_WdgPaletteDock()) , m_model(new KisPaletteModel(this)) , m_paletteChooser(new KisPaletteChooser(this)) , m_view(0) , m_resourceProvider(0) , m_rServer(KoResourceServerProvider::instance()->paletteServer()) , m_activeDocument(0) , m_paletteEditor(new KisPaletteEditor) , m_actAdd(new QAction(KisIconUtils::loadIcon("list-add"), i18n("Add a color"))) , m_actRemove(new QAction(KisIconUtils::loadIcon("edit-delete"), i18n("Delete color"))) , m_actModify(new QAction(KisIconUtils::loadIcon("edit-rename"), i18n("Modify this spot"))) , m_actEditPalette(new QAction(KisIconUtils::loadIcon("groupLayer"), i18n("Edit this palette"))) , m_colorSelfUpdate(false) { QWidget *mainWidget = new QWidget(this); setWidget(mainWidget); m_ui->setupUi(mainWidget); connect(KisResourceLocator::instance(), SIGNAL(storageRemoved()), this, SLOT(slotStoragesChanged())); m_ui->bnAdd->setDefaultAction(m_actAdd.data()); m_ui->bnRemove->setDefaultAction(m_actRemove.data()); m_ui->bnRename->setDefaultAction(m_actModify.data()); m_ui->bnEditPalette->setDefaultAction(m_actEditPalette.data()); // to make sure their icons have the same size m_ui->bnRemove->setIconSize(QSize(16, 16)); m_ui->bnRename->setIconSize(QSize(16, 16)); m_ui->bnAdd->setIconSize(QSize(16, 16)); m_ui->bnEditPalette->setIconSize(QSize(16, 16)); m_ui->paletteView->setPaletteModel(m_model); m_ui->paletteView->setAllowModification(true); m_ui->cmbNameList->setCompanionView(m_ui->paletteView); m_paletteEditor->setPaletteModel(m_model); connect(m_actAdd.data(), SIGNAL(triggered()), SLOT(slotAddColor())); connect(m_actRemove.data(), SIGNAL(triggered()), SLOT(slotRemoveColor())); connect(m_actModify.data(), SIGNAL(triggered()), SLOT(slotEditEntry())); connect(m_actEditPalette.data(), SIGNAL(triggered()), SLOT(slotEditPalette())); connect(m_ui->paletteView, SIGNAL(sigIndexSelected(QModelIndex)), SLOT(slotPaletteIndexSelected(QModelIndex))); connect(m_ui->paletteView, SIGNAL(clicked(QModelIndex)), SLOT(slotPaletteIndexClicked(QModelIndex))); connect(m_ui->paletteView, SIGNAL(doubleClicked(QModelIndex)), SLOT(slotPaletteIndexDoubleClicked(QModelIndex))); connect(m_ui->cmbNameList, SIGNAL(sigColorSelected(const KoColor&)), SLOT(slotNameListSelection(const KoColor&))); m_viewContextMenu.addAction(m_actModify.data()); m_viewContextMenu.addAction(m_actRemove.data()); connect(m_ui->paletteView, SIGNAL(pressed(QModelIndex)), SLOT(slotContextMenu(QModelIndex))); m_paletteChooser->setAllowModification(true); connect(m_paletteChooser, SIGNAL(sigPaletteSelected(KoColorSetSP)), SLOT(slotSetColorSet(KoColorSetSP))); connect(m_paletteChooser, SIGNAL(sigAddPalette()), SLOT(slotAddPalette())); connect(m_paletteChooser, SIGNAL(sigImportPalette()), SLOT(slotImportPalette())); connect(m_paletteChooser, SIGNAL(sigRemovePalette(KoColorSetSP)), SLOT(slotRemovePalette(KoColorSetSP))); connect(m_paletteChooser, SIGNAL(sigExportPalette(KoColorSetSP)), SLOT(slotExportPalette(KoColorSetSP))); m_ui->bnColorSets->setIcon(KisIconUtils::loadIcon("hi16-palette_library")); m_ui->bnColorSets->setToolTip(i18n("Choose palette")); m_ui->bnColorSets->setPopupWidget(m_paletteChooser); KisConfig cfg(true); QString defaultPaletteName = cfg.defaultPalette(); KoColorSetSP defaultPalette = m_rServer->resourceByName(defaultPaletteName); if (defaultPalette) { slotSetColorSet(defaultPalette); } else { m_ui->bnAdd->setEnabled(false); m_ui->bnRename->setEnabled(false); m_ui->bnRemove->setEnabled(false); m_ui->bnEditPalette->setEnabled(false); m_ui->paletteView->setAllowModification(false); } KoResourceServer *srv = KoResourceServerProvider::instance()->paletteServer(); srv->addObserver(this); } PaletteDockerDock::~PaletteDockerDock() -{ } +{ + KoResourceServer *srv = KoResourceServerProvider::instance()->paletteServer(); + srv->removeObserver(this); +} void PaletteDockerDock::setViewManager(KisViewManager* kisview) { m_view = kisview; m_resourceProvider = kisview->canvasResourceProvider(); connect(m_resourceProvider, SIGNAL(sigSavingWorkspace(KisWorkspaceResourceSP)), SLOT(saveToWorkspace(KisWorkspaceResourceSP))); connect(m_resourceProvider, SIGNAL(sigLoadingWorkspace(KisWorkspaceResourceSP)), SLOT(loadFromWorkspace(KisWorkspaceResourceSP))); connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)), this, SLOT(slotFGColorResourceChanged(KoColor))); kisview->nodeManager()->disconnect(m_model); } void PaletteDockerDock::unsetResourceServer() { KoResourceServer *srv = KoResourceServerProvider::instance()->paletteServer(); srv->removeObserver(this); } void PaletteDockerDock::resourceAdded(QSharedPointer resource) { Q_UNUSED(resource); } void PaletteDockerDock::removingResource(QSharedPointer resource) { Q_UNUSED(resource); } void PaletteDockerDock::resourceChanged(QSharedPointer resource) { Q_UNUSED(resource); m_model->sigPaletteModified(); } void PaletteDockerDock::syncTaggedResourceView() {} void PaletteDockerDock::syncTagAddition(const QString& tag) { Q_UNUSED(tag); } void PaletteDockerDock::syncTagRemoval(const QString& tag) { Q_UNUSED(tag); } void PaletteDockerDock::slotContextMenu(const QModelIndex &) { if (QApplication::mouseButtons() == Qt::RightButton) { m_viewContextMenu.exec(QCursor::pos()); } } void PaletteDockerDock::slotAddPalette() { m_paletteEditor->addPalette(); } void PaletteDockerDock::slotRemovePalette(KoColorSetSP cs) { m_paletteEditor->removePalette(cs); } void PaletteDockerDock::slotImportPalette() { m_paletteEditor->importPalette(); } void PaletteDockerDock::slotExportPalette(KoColorSetSP palette) { KoFileDialog dialog(this, KoFileDialog::SaveFile, "Save Palette"); dialog.setDefaultDir(palette->filename()); dialog.setMimeTypeFilters(QStringList() << "krita/x-colorset"); QString newPath; QString oriPath = palette->filename(); if ((newPath = dialog.filename()).isEmpty()) { return; } palette->setFilename(newPath); palette->save(); palette->setFilename(oriPath); } void PaletteDockerDock::setCanvas(KoCanvasBase *canvas) { setEnabled(canvas != 0); if (canvas) { KisCanvas2 *cv = qobject_cast(canvas); m_ui->paletteView->setDisplayRenderer(cv->displayColorConverter()->displayRendererInterface()); } if (m_view && m_view->document()) { m_activeDocument = m_view->document(); m_paletteEditor->setView(m_view); } if (!m_currentColorSet) { slotSetColorSet(0); } } void PaletteDockerDock::unsetCanvas() { setEnabled(false); m_ui->paletteView->setDisplayRenderer(0); m_paletteEditor->setView(0); if (!m_currentColorSet) { slotSetColorSet(0); } } void PaletteDockerDock::slotSetColorSet(KoColorSetSP colorSet) { if (colorSet && colorSet->isEditable()) { m_ui->bnAdd->setEnabled(true); m_ui->bnRename->setEnabled(true); m_ui->bnRemove->setEnabled(true); m_ui->bnEditPalette->setEnabled(true); m_ui->paletteView->setAllowModification(true); } else { m_ui->bnAdd->setEnabled(false); m_ui->bnRename->setEnabled(false); m_ui->bnRemove->setEnabled(false); m_ui->bnEditPalette->setEnabled(false); m_ui->paletteView->setAllowModification(false); } m_currentColorSet = colorSet; m_model->setPalette(colorSet); if (colorSet) { KisConfig cfg(true); cfg.setDefaultPalette(colorSet->name()); m_ui->lblPaletteName->setTextElideMode(Qt::ElideLeft); m_ui->lblPaletteName->setText(colorSet->name()); } else { m_ui->lblPaletteName->setText(""); } } void PaletteDockerDock::slotEditPalette() { KisDlgPaletteEditor dlg; if (!m_currentColorSet) { return; } dlg.setPaletteModel(m_model); dlg.setView(m_view); if (dlg.exec() != QDialog::Accepted){ return; } slotSetColorSet(m_currentColorSet); // update GUI } void PaletteDockerDock::slotAddColor() { if (m_resourceProvider) { m_paletteEditor->addEntry(m_resourceProvider->fgColor()); } } void PaletteDockerDock::slotRemoveColor() { QModelIndex index = m_ui->paletteView->currentIndex(); if (!index.isValid()) { return; } m_paletteEditor->removeEntry(index); m_ui->bnRemove->setEnabled(false); } void PaletteDockerDock::setFGColorByPalette(const KisSwatch &entry) { if (m_resourceProvider) { m_colorSelfUpdate = true; m_resourceProvider->setFGColor(entry.color()); m_colorSelfUpdate = false; } } void PaletteDockerDock::saveToWorkspace(KisWorkspaceResourceSP workspace) { if (!m_currentColorSet.isNull()) { workspace->setProperty("palette", m_currentColorSet->name()); } } void PaletteDockerDock::loadFromWorkspace(KisWorkspaceResourceSP workspace) { if (workspace->hasProperty("palette")) { KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); KoColorSetSP colorSet = rServer->resourceByName(workspace->getString("palette")); if (colorSet) { slotSetColorSet(colorSet); } } } void PaletteDockerDock::slotFGColorResourceChanged(const KoColor &color) { if (!m_colorSelfUpdate) { m_ui->paletteView->slotFGColorChanged(color); } } void PaletteDockerDock::slotStoragesChanged() { if (m_activeDocument.isNull()) { slotSetColorSet(0); } if (m_currentColorSet) { if (!m_rServer->resourceByFilename(m_currentColorSet->filename())) { slotSetColorSet(0); } } } void PaletteDockerDock::slotPaletteIndexSelected(const QModelIndex &index) { bool occupied = qvariant_cast(index.data(KisPaletteModel::CheckSlotRole)); if (occupied) { if (!qvariant_cast(index.data(KisPaletteModel::IsGroupNameRole))) { m_ui->bnRemove->setEnabled(true); KisSwatch entry = m_model->getEntry(index); setFGColorByPalette(entry); } } if (!m_currentColorSet->isEditable()) { return; } m_ui->bnRemove->setEnabled(occupied); } void PaletteDockerDock::slotPaletteIndexClicked(const QModelIndex &index) { if (!(qvariant_cast(index.data(KisPaletteModel::CheckSlotRole)))) { setEntryByForeground(index); } } void PaletteDockerDock::slotPaletteIndexDoubleClicked(const QModelIndex &index) { m_paletteEditor->modifyEntry(index); } void PaletteDockerDock::setEntryByForeground(const QModelIndex &index) { m_paletteEditor->setEntry(m_resourceProvider->fgColor(), index); if (m_currentColorSet->isEditable()) { m_ui->bnRemove->setEnabled(true); } } void PaletteDockerDock::slotEditEntry() { QModelIndex index = m_ui->paletteView->currentIndex(); if (!index.isValid()) { return; } m_paletteEditor->modifyEntry(index); } void PaletteDockerDock::slotNameListSelection(const KoColor &color) { m_colorSelfUpdate = true; m_ui->paletteView->selectClosestColor(color); m_resourceProvider->setFGColor(color); m_colorSelfUpdate = false; } diff --git a/plugins/impex/xcf/kis_xcf_import.cpp b/plugins/impex/xcf/kis_xcf_import.cpp index 97f6b483a1..5325845801 100644 --- a/plugins/impex/xcf/kis_xcf_import.cpp +++ b/plugins/impex/xcf/kis_xcf_import.cpp @@ -1,331 +1,331 @@ /* * Copyright (c) 2009 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_xcf_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include "kis_types.h" #include extern "C" { #include "xcftools.h" #include "pixels.h" #define GET_RED(x) (x >> RED_SHIFT) #define GET_GREEN(x) (x >> GREEN_SHIFT) #define GET_BLUE(x) (x >> BLUE_SHIFT) #define GET_ALPHA(x) (x >> ALPHA_SHIFT) } QString layerModeG2K(GimpLayerModeEffects mode) { switch (mode) { case GIMP_NORMAL_MODE: return COMPOSITE_OVER; case GIMP_DISSOLVE_MODE: return COMPOSITE_DISSOLVE; case GIMP_MULTIPLY_MODE: return COMPOSITE_MULT; case GIMP_SCREEN_MODE: return COMPOSITE_SCREEN; case GIMP_OVERLAY_MODE: case GIMP_SOFTLIGHT_MODE: return COMPOSITE_OVERLAY; case GIMP_DIFFERENCE_MODE: return COMPOSITE_DIFF; case GIMP_ADDITION_MODE: return COMPOSITE_ADD; case GIMP_SUBTRACT_MODE: return COMPOSITE_SUBTRACT; case GIMP_DARKEN_ONLY_MODE: return COMPOSITE_DARKEN; case GIMP_LIGHTEN_ONLY_MODE: return COMPOSITE_LIGHTEN; case GIMP_HUE_MODE: return COMPOSITE_HUE_HSL; case GIMP_SATURATION_MODE: return COMPOSITE_SATURATION_HSV; case GIMP_COLOR_MODE: return COMPOSITE_COLOR_HSL; case GIMP_VALUE_MODE: return COMPOSITE_VALUE; case GIMP_DIVIDE_MODE: return COMPOSITE_DIVIDE; case GIMP_DODGE_MODE: return COMPOSITE_DODGE; case GIMP_BURN_MODE: return COMPOSITE_BURN; case GIMP_ERASE_MODE: return COMPOSITE_ERASE; case GIMP_REPLACE_MODE: return COMPOSITE_COPY; case GIMP_HARDLIGHT_MODE: return COMPOSITE_HARD_LIGHT; case GIMP_COLOR_ERASE_MODE: case GIMP_NORMAL_NOPARTIAL_MODE: case GIMP_ANTI_ERASE_MODE: case GIMP_GRAIN_EXTRACT_MODE: return COMPOSITE_GRAIN_EXTRACT; case GIMP_GRAIN_MERGE_MODE: return COMPOSITE_GRAIN_MERGE; case GIMP_BEHIND_MODE: break; } dbgFile << "Unknown mode: " << mode; return COMPOSITE_OVER; } struct Layer { KisLayerSP layer; int depth; KisMaskSP mask; }; KisGroupLayerSP findGroup(const QVector &layers, const Layer& layer, int i) { for (; i < layers.size(); ++i) { KisGroupLayerSP group = dynamic_cast(const_cast(layers[i].layer.data())); if (group && (layers[i].depth == layer.depth -1)) { return group; } } return 0; } void addLayers(const QVector &layers, KisImageSP image, int depth) { for(int i = 0; i < layers.size(); i++) { const Layer &layer = layers[i]; if (layer.depth == depth) { KisGroupLayerSP group = (depth == 0 ? image->rootLayer() : findGroup(layers, layer, i)); image->addNode(layer.layer, group); if (layer.mask) { image->addNode(layer.mask, layer.layer); } } } } K_PLUGIN_FACTORY_WITH_JSON(XCFImportFactory, "krita_xcf_import.json", registerPlugin();) KisXCFImport::KisXCFImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisXCFImport::~KisXCFImport() { } KisImportExportErrorCode KisXCFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { int errorStatus; dbgFile << "Start decoding file"; QByteArray data = io->readAll(); xcf_file = (uint8_t*)data.data(); xcf_length = data.size(); io->close(); // Decode the data if (getBasicXcfInfo() != XCF_OK) { if (XCF.version < 0 || XCF.version > 3) { document->setErrorMessage(i18n("This XCF file is too new; Krita cannot support XCF files written by GIMP 2.9 or newer.")); return ImportExportCodes::FormatFeaturesUnsupported; } return ImportExportCodes::FileFormatIncorrect; } if(initColormap() != XCF_OK) { return ImportExportCodes::FileFormatIncorrect; } dbgFile << XCF.version << "width = " << XCF.width << "height = " << XCF.height << "layers = " << XCF.numLayers; // Create the image KisImageSP image = new KisImage(document->createUndoStore(), XCF.width, XCF.height, KoColorSpaceRegistry::instance()->rgb8(), "built image"); QVector layers; uint maxDepth = 0; // Read layers for (int i = 0; i < XCF.numLayers; ++i) { Layer layer; xcfLayer& xcflayer = XCF.layers[i]; dbgFile << i << " name = " << xcflayer.name << " opacity = " << xcflayer.opacity << "group:" << xcflayer.isGroup << xcflayer.pathLength; dbgFile << ppVar(xcflayer.dim.width) << ppVar(xcflayer.dim.height) << ppVar(xcflayer.dim.tilesx) << ppVar(xcflayer.dim.tilesy) << ppVar(xcflayer.dim.ntiles) << ppVar(xcflayer.dim.c.t) << ppVar(xcflayer.dim.c.l) << ppVar(xcflayer.dim.c.r) << ppVar(xcflayer.dim.c.b); maxDepth = qMax(maxDepth, xcflayer.pathLength); bool isRgbA = false; // Select the color space const KoColorSpace* colorSpace = 0; switch (xcflayer.type) { case GIMP_INDEXED_IMAGE: case GIMP_INDEXEDA_IMAGE: case GIMP_RGB_IMAGE: case GIMP_RGBA_IMAGE: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); isRgbA = true; break; case GIMP_GRAY_IMAGE: case GIMP_GRAYA_IMAGE: colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), ""); isRgbA = false; break; } // Create the layer KisLayerSP kisLayer; if (xcflayer.isGroup) { kisLayer = new KisGroupLayer(image, QString::fromUtf8(xcflayer.name), xcflayer.opacity); } else { kisLayer = new KisPaintLayer(image, QString::fromUtf8(xcflayer.name), xcflayer.opacity, colorSpace); } // Set some properties kisLayer->setCompositeOpId(layerModeG2K(xcflayer.mode)); kisLayer->setVisible(xcflayer.isVisible); kisLayer->disableAlphaChannel(xcflayer.mode != GIMP_NORMAL_MODE); layer.layer = kisLayer; layer.depth = xcflayer.pathLength; // Copy the data in the image if ((errorStatus = initLayer(&xcflayer)) != XCF_OK) { return ImportExportCodes::FileFormatIncorrect; } int left = xcflayer.dim.c.l; int top = xcflayer.dim.c.t; if (!xcflayer.isGroup) { // Copy the data; for (unsigned int x = 0; x < xcflayer.dim.width; x += TILE_WIDTH) { for (unsigned int y = 0; y < xcflayer.dim.height; y += TILE_HEIGHT) { rect want; want.l = x + left; want.t = y + top; want.b = want.t + TILE_HEIGHT; want.r = want.l + TILE_WIDTH; Tile* tile = getMaskOrLayerTile(&xcflayer.dim, &xcflayer.pixels, want); if (tile == XCF_PTR_EMPTY) { return ImportExportCodes::FileFormatIncorrect; } KisHLineIteratorSP it = kisLayer->paintDevice()->createHLineIteratorNG(x, y, TILE_WIDTH); rgba* data = tile->pixels; for (int v = 0; v < TILE_HEIGHT; ++v) { if (isRgbA) { // RGB image do { KoBgrTraits::setRed(it->rawData(), GET_RED(*data)); KoBgrTraits::setGreen(it->rawData(), GET_GREEN(*data)); KoBgrTraits::setBlue(it->rawData(), GET_BLUE(*data)); KoBgrTraits::setOpacity(it->rawData(), quint8(GET_ALPHA(*data)), 1); ++data; } while (it->nextPixel()); } else { // Grayscale image do { it->rawData()[0] = GET_RED(*data); it->rawData()[1] = GET_ALPHA(*data); ++data; } while (it->nextPixel()); } it->nextRow(); } } } // Move the layer to its position kisLayer->paintDevice()->setX(left); kisLayer->paintDevice()->setY(top); } // Create the mask if (xcflayer.hasMask) { KisTransparencyMaskSP mask = new KisTransparencyMask(); layer.mask = mask; mask->initSelection(kisLayer); for (unsigned int x = 0; x < xcflayer.dim.width; x += TILE_WIDTH) { for (unsigned int y = 0; y < xcflayer.dim.height; y += TILE_HEIGHT) { rect want; want.l = x + left; want.t = y + top; want.b = want.t + TILE_HEIGHT; want.r = want.l + TILE_WIDTH; Tile* tile = getMaskOrLayerTile(&xcflayer.dim, &xcflayer.mask, want); if (tile == XCF_PTR_EMPTY) { - delete tile; + free(tile); return ImportExportCodes::FileFormatIncorrect; } KisHLineIteratorSP it = mask->paintDevice()->createHLineIteratorNG(x, y, TILE_WIDTH); rgba* data = tile->pixels; for (int v = 0; v < TILE_HEIGHT; ++v) { do { it->rawData()[0] = GET_ALPHA(*data); ++data; } while (it->nextPixel()); it->nextRow(); } - delete tile; + free(tile); } } mask->paintDevice()->setX(left); mask->paintDevice()->setY(top); } dbgFile << xcflayer.pixels.tileptrs; layers.append(layer); } for (uint i = 0; i <= maxDepth; ++i) { addLayers(layers, image, i); } document->setCurrentImage(image); return ImportExportCodes::OK; } #include "kis_xcf_import.moc" diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.cpp b/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.cpp index acd1038d02..332f85f244 100644 --- a/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.cpp +++ b/plugins/paintops/libpaintop/kis_brush_based_paintop_options_widget.cpp @@ -1,58 +1,58 @@ /* * Copyright (c) 2010 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_based_paintop_options_widget.h" #include "kis_brush_option_widget.h" #include KisBrushBasedPaintopOptionWidget::KisBrushBasedPaintopOptionWidget(QWidget* parent) : KisPaintOpSettingsWidget(parent) { m_brushOption = new KisBrushOptionWidget(); addPaintOpOption(m_brushOption, i18n("Brush Tip")); } KisBrushBasedPaintopOptionWidget::~KisBrushBasedPaintopOptionWidget() { } void KisBrushBasedPaintopOptionWidget::setPrecisionEnabled(bool value) { m_brushOption->setPrecisionEnabled(value); } void KisBrushBasedPaintopOptionWidget::setHSLBrushTipEnabled(bool value) { - m_brushOption->setHSLBrusTipEnabled(value); + m_brushOption->setHSLBrushTipEnabled(value); } KisBrushSP KisBrushBasedPaintopOptionWidget::brush() { return m_brushOption->brush(); } bool KisBrushBasedPaintopOptionWidget::presetIsValid() { return m_brushOption->presetIsValid(); } KisBrushOptionWidget *KisBrushBasedPaintopOptionWidget::brushOptionWidget() const { return m_brushOption; } diff --git a/plugins/paintops/libpaintop/kis_brush_chooser.cpp b/plugins/paintops/libpaintop/kis_brush_chooser.cpp index cc3daf0d3d..80b7261533 100644 --- a/plugins/paintops/libpaintop/kis_brush_chooser.cpp +++ b/plugins/paintops/libpaintop/kis_brush_chooser.cpp @@ -1,585 +1,585 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2009 Sven Langkamp * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Srikanth Tiyyagura * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisBrushServerProvider.h" #include "kis_slider_spin_box.h" #include "widgets/kis_multipliers_double_slider_spinbox.h" #include "kis_spacing_selection_widget.h" #include "kis_signals_blocker.h" #include "kis_imagepipe_brush.h" #include "kis_custom_brush_widget.h" #include "kis_clipboard_brush_widget.h" #include #include "kis_global.h" #include "kis_gbr_brush.h" #include "kis_png_brush.h" #include "kis_debug.h" #include "kis_image.h" #include /// The resource item delegate for rendering the resource preview class KisBrushDelegate : public QAbstractItemDelegate { public: KisBrushDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) {} ~KisBrushDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } }; void KisBrushDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { if (! index.isValid()) return; QImage thumbnail = index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); QRect itemRect = option.rect; if (thumbnail.height() > itemRect.height() || thumbnail.width() > itemRect.width()) { thumbnail = thumbnail.scaled(itemRect.size() , Qt::KeepAspectRatio, Qt::SmoothTransformation); } painter->save(); int dx = (itemRect.width() - thumbnail.width()) / 2; int dy = (itemRect.height() - thumbnail.height()) / 2; painter->drawImage(itemRect.x() + dx, itemRect.y() + dy, thumbnail); if (option.state & QStyle::State_Selected) { painter->setPen(QPen(option.palette.highlight(), 2.0)); painter->drawRect(option.rect); painter->setCompositionMode(QPainter::CompositionMode_HardLight); painter->setOpacity(0.65); painter->fillRect(option.rect, option.palette.highlight()); } painter->restore(); } KisPredefinedBrushChooser::KisPredefinedBrushChooser(QWidget *parent, const char *name) : QWidget(parent), m_stampBrushWidget(0), m_clipboardBrushWidget(0) { setObjectName(name); setupUi(this); brushSizeSpinBox->setRange(0, KSharedConfig::openConfig()->group("").readEntry("maximumBrushSize", 1000), 2); brushSizeSpinBox->setValue(5); brushSizeSpinBox->setExponentRatio(3.0); brushSizeSpinBox->setSuffix(i18n(" px")); brushSizeSpinBox->setExponentRatio(3.0); QObject::connect(brushSizeSpinBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemSize(qreal))); brushRotationSpinBox->setRange(0, 360, 0); brushRotationSpinBox->setValue(0); brushRotationSpinBox->setSuffix(QChar(Qt::Key_degree)); QObject::connect(brushRotationSpinBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemRotation(qreal))); brushSpacingSelectionWidget->setSpacing(true, 1.0); connect(brushSpacingSelectionWidget, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged())); m_itemChooser = new KisResourceItemChooser(ResourceType::Brushes, false, this); m_itemChooser->setObjectName("brush_selector"); m_itemChooser->showTaggingBar(true); m_itemChooser->setRowHeight(30); m_itemChooser->setItemDelegate(new KisBrushDelegate(this)); m_itemChooser->setCurrentItem(0); m_itemChooser->setSynced(true); m_itemChooser->setMinimumWidth(100); m_itemChooser->setMinimumHeight(150); m_itemChooser->showButtons(false); // turn the import and delete buttons since we want control over them addPresetButton->setIcon(KisIconUtils::loadIcon("list-add")); deleteBrushTipButton->setIcon(KisIconUtils::loadIcon("trash-empty")); connect(addPresetButton, SIGNAL(clicked(bool)), this, SLOT(slotImportNewBrushResource())); connect(deleteBrushTipButton, SIGNAL(clicked(bool)), this, SLOT(slotDeleteBrushResource())); presetsLayout->addWidget(m_itemChooser); connect(m_itemChooser, SIGNAL(resourceSelected(KoResourceSP )), this, SLOT(updateBrushTip(KoResourceSP ))); stampButton->setIcon(KisIconUtils::loadIcon("list-add")); stampButton->setToolTip(i18n("Creates a brush tip from the current image selection." "\n If no selection is present the whole image will be used.")); clipboardButton->setIcon(KisIconUtils::loadIcon("list-add")); clipboardButton->setToolTip(i18n("Creates a brush tip from the image in the clipboard.")); connect(stampButton, SIGNAL(clicked()), this, SLOT(slotOpenStampBrush())); connect(clipboardButton, SIGNAL(clicked()), SLOT(slotOpenClipboardBrush())); QGridLayout *spacingLayout = new QGridLayout(); spacingLayout->setObjectName("spacing grid layout"); resetBrushButton->setToolTip(i18n("Reloads Spacing from file\nSets Scale to 1.0\nSets Rotation to 0.0")); connect(resetBrushButton, SIGNAL(clicked()), SLOT(slotResetBrush())); intAdjustmentMidPoint->setRange(0, 255); intAdjustmentMidPoint->setPageStep(10); intAdjustmentMidPoint->setSingleStep(1); intAdjustmentMidPoint->setPrefix(i18nc("@label:slider", "Neutral point: ")); intBrightnessAdjustment->setRange(-100, 100); intBrightnessAdjustment->setPageStep(10); intBrightnessAdjustment->setSingleStep(1); intBrightnessAdjustment->setSuffix("%"); intBrightnessAdjustment->setPrefix(i18nc("@label:slider", "Brightness: ")); intContrastAdjustment->setRange(-100, 100); intContrastAdjustment->setPageStep(10); intContrastAdjustment->setSingleStep(1); intContrastAdjustment->setSuffix("%"); intContrastAdjustment->setPrefix(i18nc("@label:slider", "Contrast: ")); btnResetAdjustments->setToolTip(i18nc("@info:tooltip", "Resets all the adjustments to default values:\n Neutral Point: 127\n Brightness: 0%\n Contrast: 0%")); connect(btnResetAdjustments, SIGNAL(clicked()), SLOT(slotResetAdjustments())); connect(btnMaskMode, SIGNAL(toggled(bool)), SLOT(slotUpdateBrushAdjustmentsState())); connect(btnColorMode, SIGNAL(toggled(bool)), SLOT(slotUpdateBrushAdjustmentsState())); connect(btnLightnessMode, SIGNAL(toggled(bool)), SLOT(slotUpdateBrushAdjustmentsState())); connect(btnMaskMode, SIGNAL(toggled(bool)), SLOT(slotWriteBrushMode())); connect(btnColorMode, SIGNAL(toggled(bool)), SLOT(slotWriteBrushMode())); connect(btnLightnessMode, SIGNAL(toggled(bool)), SLOT(slotWriteBrushMode())); connect(btnMaskMode, SIGNAL(toggled(bool)), SLOT(slotUpdateResetBrushAdjustmentsButtonState())); connect(btnColorMode, SIGNAL(toggled(bool)), SLOT(slotUpdateResetBrushAdjustmentsButtonState())); connect(btnLightnessMode, SIGNAL(toggled(bool)), SLOT(slotUpdateResetBrushAdjustmentsButtonState())); connect(intAdjustmentMidPoint, SIGNAL(valueChanged(int)), SLOT(slotWriteBrushAdjustments())); connect(intBrightnessAdjustment, SIGNAL(valueChanged(int)), SLOT(slotWriteBrushAdjustments())); connect(intContrastAdjustment, SIGNAL(valueChanged(int)), SLOT(slotWriteBrushAdjustments())); connect(intAdjustmentMidPoint, SIGNAL(valueChanged(int)), SLOT(slotUpdateResetBrushAdjustmentsButtonState())); connect(intBrightnessAdjustment, SIGNAL(valueChanged(int)), SLOT(slotUpdateResetBrushAdjustmentsButtonState())); connect(intContrastAdjustment, SIGNAL(valueChanged(int)), SLOT(slotUpdateResetBrushAdjustmentsButtonState())); updateBrushTip(m_itemChooser->currentResource()); } KisPredefinedBrushChooser::~KisPredefinedBrushChooser() { } void KisPredefinedBrushChooser::setBrush(KisBrushSP brush) { /** * Warning: since the brushes are always cloned after loading from XML or * fetching from the server, we cannot just ask for that brush explicitly. * Instead, we should search for the brush with the same filename and/or name * and load it. Please take it into account that after selecting the brush * explicitly in the chooser, m_itemChooser->currentResource() might be * **not** the same as the value in m_brush. * * Ideally, if the resource is not found on the server, we should add it, but * it might lead to a set of weird consequences. So for now we just * select nothing. */ KoResourceServer* server = KisBrushServerProvider::instance()->brushServer(); KoResourceSP resource = server->resourceByFilename(brush->filename()); if (!resource) { resource = server->resourceByName(brush->name()); } if (!resource) { resource = brush; } m_itemChooser->setCurrentResource(resource); updateBrushTip(brush, true); } void KisPredefinedBrushChooser::slotResetBrush() { /** * The slot also resets the brush on the server * * TODO: technically, after we refactored all the brushes to be forked, * we can just re-update the brush from the server without reloading. * But it needs testing. */ KisBrushSP brush = m_itemChooser->currentResource().dynamicCast(); if (brush) { brush->load(KisGlobalResourcesInterface::instance()); brush->setScale(1.0); brush->setAngle(0.0); if (KisColorfulBrush *colorfulBrush = dynamic_cast(m_brush.data())) { colorfulBrush->setUseColorAsMask(false); colorfulBrush->setPreserveLightness(false); colorfulBrush->setAdjustmentMidPoint(127); colorfulBrush->setBrightnessAdjustment(0.0); colorfulBrush->setContrastAdjustment(0.0); } updateBrushTip(brush); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSetItemSize(qreal sizeValue) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { int brushWidth = m_brush->width(); m_brush->setScale(sizeValue / qreal(brushWidth)); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSetItemRotation(qreal rotationValue) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { m_brush->setAngle(rotationValue / 180.0 * M_PI); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSpacingChanged() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { m_brush->setSpacing(brushSpacingSelectionWidget->spacing()); m_brush->setAutoSpacing(brushSpacingSelectionWidget->autoSpacingActive(), brushSpacingSelectionWidget->autoSpacingCoeff()); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotOpenStampBrush() { if(!m_stampBrushWidget) { m_stampBrushWidget = new KisCustomBrushWidget(this, i18n("Stamp"), m_image); m_stampBrushWidget->setModal(false); connect(m_stampBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResourceSP )), SLOT(slotNewPredefinedBrush(KoResourceSP ))); } else { m_stampBrushWidget->setImage(m_image); } QDialog::DialogCode result = (QDialog::DialogCode)m_stampBrushWidget->exec(); if(result) { updateBrushTip(m_itemChooser->currentResource()); } } void KisPredefinedBrushChooser::slotOpenClipboardBrush() { if(!m_clipboardBrushWidget) { m_clipboardBrushWidget = new KisClipboardBrushWidget(this, i18n("Clipboard"), m_image); m_clipboardBrushWidget->setModal(true); connect(m_clipboardBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResourceSP )), SLOT(slotNewPredefinedBrush(KoResourceSP ))); } QDialog::DialogCode result = (QDialog::DialogCode)m_clipboardBrushWidget->exec(); if(result) { updateBrushTip(m_itemChooser->currentResource()); } } void KisPredefinedBrushChooser::updateBrushTip(KoResourceSP resource, bool isChangingBrushPresets) { QString animatedBrushTipSelectionMode; // incremental, random, etc { KisBrushSP brush = resource.dynamicCast(); m_brush = brush ? brush->clone().dynamicCast() : 0; } if (m_brush) { brushTipNameLabel->setText(i18n(m_brush->name().toUtf8().data())); QString brushTypeString = ""; if (m_brush->brushType() == INVALID) { brushTypeString = i18n("Invalid"); } else if (m_brush->brushType() == MASK) { brushTypeString = i18n("Mask"); } else if (m_brush->brushType() == IMAGE) { brushTypeString = i18n("GBR"); } else if (m_brush->brushType() == PIPE_MASK ) { brushTypeString = i18n("Animated Mask"); // GIH brush // cast to GIH brush and grab parasite name //m_brush KisImagePipeBrushSP pipeBrush = resource.dynamicCast(); animatedBrushTipSelectionMode = pipeBrush->parasiteSelection(); } else if (m_brush->brushType() == PIPE_IMAGE ) { brushTypeString = i18n("Animated Image"); } QString brushDetailsText = QString("%1 (%2 x %3) %4") .arg(brushTypeString) .arg(m_brush->width()) .arg(m_brush->height()) .arg(animatedBrushTipSelectionMode); brushDetailsLabel->setText(brushDetailsText); // keep the current preset's tip settings if we are preserving it // this will set the brush's model data to keep what it currently has for size, spacing, etc. if (preserveBrushPresetSettings->isChecked() && !isChangingBrushPresets) { m_brush->setAutoSpacing(brushSpacingSelectionWidget->autoSpacingActive(), brushSpacingSelectionWidget->autoSpacingCoeff()); m_brush->setAngle(brushRotationSpinBox->value() * M_PI / 180); m_brush->setSpacing(brushSpacingSelectionWidget->spacing()); m_brush->setUserEffectiveSize(brushSizeSpinBox->value()); } brushSpacingSelectionWidget->setSpacing(m_brush->autoSpacingActive(), m_brush->autoSpacingActive() ? m_brush->autoSpacingCoeff() : m_brush->spacing()); brushRotationSpinBox->setValue(m_brush->angle() * 180 / M_PI); brushSizeSpinBox->setValue(m_brush->width() * m_brush->scale()); emit sigBrushChanged(); } slotUpdateBrushModeButtonsState(); } #include "kis_scaling_size_brush.h" void KisPredefinedBrushChooser::slotUpdateBrushModeButtonsState() { KisColorfulBrush *colorfulBrush = dynamic_cast(m_brush.data()); const bool modeSwitchEnabled = m_hslBrushTipEnabled && colorfulBrush && colorfulBrush->hasColor(); if (modeSwitchEnabled) { if (colorfulBrush->useColorAsMask() && colorfulBrush->preserveLightness()) { btnLightnessMode->setChecked(true); } else if (colorfulBrush->useColorAsMask()) { btnMaskMode->setChecked(true); } else { btnColorMode->setChecked(true); } { // sliders emit update signals when modified from the code KisSignalsBlocker b(intAdjustmentMidPoint, intBrightnessAdjustment, intContrastAdjustment); intAdjustmentMidPoint->setValue(colorfulBrush->adjustmentMidPoint()); intBrightnessAdjustment->setValue(qRound(colorfulBrush->brightnessAdjustment() * 100.0)); intContrastAdjustment->setValue(qRound(colorfulBrush->contrastAdjustment() * 100.0)); } btnMaskMode->setToolTip(i18nc("@info:tooltip", "Luminosity of the brush tip image is used as alpha channel for the stroke")); btnColorMode->setToolTip(i18nc("@info:tooltip", "The brush tip image is painted as it is")); btnLightnessMode->setToolTip(i18nc("@info:tooltip", "Luminosity of the brush tip image is used as lightness correction for the painting color. Alpha channel of the brush tip image is used as alpha for the final stroke")); intAdjustmentMidPoint->setToolTip(i18nc("@info:tooltip", "Luminosity value of the brush that will not change the painting color. All brush pixels darker than neutral point will paint with darker color, pixels lighter than neutral point — lighter.")); intBrightnessAdjustment->setToolTip(i18nc("@info:tooltip", "Brightness correction for the brush")); intContrastAdjustment->setToolTip(i18nc("@info:tooltip", "Contrast correction for the brush")); grpBrushMode->setToolTip(""); } else { { // sliders emit update signals when modified from the code KisSignalsBlocker b(intAdjustmentMidPoint, intBrightnessAdjustment, intContrastAdjustment); intAdjustmentMidPoint->setValue(127); intBrightnessAdjustment->setValue(0); intContrastAdjustment->setValue(0); } btnMaskMode->setChecked(true); btnMaskMode->setToolTip(""); btnColorMode->setToolTip(""); btnLightnessMode->setToolTip(""); intAdjustmentMidPoint->setToolTip(""); intBrightnessAdjustment->setToolTip(""); intContrastAdjustment->setToolTip(""); if (m_hslBrushTipEnabled) { grpBrushMode->setToolTip(i18nc("@info:tooltip", "The selected brush tip does not have color channels. The brush will work in \"Mask\" mode.")); } else { grpBrushMode->setToolTip(i18nc("@info:tooltip", "The selected brush engine does not support \"Color\" or \"Lightness\" modes. The brush will work in \"Mask\" mode.")); } } grpBrushMode->setEnabled(modeSwitchEnabled); slotUpdateBrushAdjustmentsState(); slotUpdateResetBrushAdjustmentsButtonState(); } void KisPredefinedBrushChooser::slotUpdateBrushAdjustmentsState() { const bool adjustmentsEnabled = btnLightnessMode->isEnabled() && btnLightnessMode->isChecked(); intAdjustmentMidPoint->setEnabled(adjustmentsEnabled); intBrightnessAdjustment->setEnabled(adjustmentsEnabled); intContrastAdjustment->setEnabled(adjustmentsEnabled); } void KisPredefinedBrushChooser::slotUpdateResetBrushAdjustmentsButtonState() { const bool adjustmentsEnabled = btnLightnessMode->isEnabled() && btnLightnessMode->isChecked(); const bool adjustmentsDefault = intAdjustmentMidPoint->value() == 127 && intBrightnessAdjustment->value() == 0 && intContrastAdjustment->value() == 0; btnResetAdjustments->setEnabled(!adjustmentsDefault && adjustmentsEnabled); } void KisPredefinedBrushChooser::slotWriteBrushMode() { KisColorfulBrush *colorfulBrush = dynamic_cast(m_brush.data()); if (!colorfulBrush) return; if (btnLightnessMode->isChecked()) { colorfulBrush->setUseColorAsMask(true); colorfulBrush->setPreserveLightness(true); } else if (btnMaskMode->isChecked()) { colorfulBrush->setUseColorAsMask(true); colorfulBrush->setPreserveLightness(false); } else { colorfulBrush->setUseColorAsMask(false); colorfulBrush->setPreserveLightness(false); } emit sigBrushChanged(); } void KisPredefinedBrushChooser::slotWriteBrushAdjustments() { KisColorfulBrush *colorfulBrush = dynamic_cast(m_brush.data()); if (!colorfulBrush) return; { // sliders emit update signals when modified from the code KisSignalsBlocker b(intAdjustmentMidPoint, intBrightnessAdjustment, intContrastAdjustment); colorfulBrush->setAdjustmentMidPoint(quint8(intAdjustmentMidPoint->value())); colorfulBrush->setBrightnessAdjustment(intBrightnessAdjustment->value() / 100.0); colorfulBrush->setContrastAdjustment(intContrastAdjustment->value() / 100.0); } emit sigBrushChanged(); } void KisPredefinedBrushChooser::slotResetAdjustments() { intAdjustmentMidPoint->setValue(127); intBrightnessAdjustment->setValue(0); intContrastAdjustment->setValue(0); slotWriteBrushAdjustments(); } void KisPredefinedBrushChooser::slotNewPredefinedBrush(KoResourceSP resource) { m_itemChooser->setCurrentResource(resource); updateBrushTip(resource); } void KisPredefinedBrushChooser::setBrushSize(qreal xPixels, qreal yPixels) { Q_UNUSED(yPixels); qreal oldWidth = m_brush->width() * m_brush->scale(); qreal newWidth = oldWidth + xPixels; newWidth = qMax(newWidth, qreal(0.1)); brushSizeSpinBox->setValue(newWidth); } void KisPredefinedBrushChooser::setImage(KisImageWSP image) { m_image = image; } -void KisPredefinedBrushChooser::setHSLBrusTipEnabled(bool value) +void KisPredefinedBrushChooser::setHSLBrushTipEnabled(bool value) { m_hslBrushTipEnabled = value; } bool KisPredefinedBrushChooser::hslBrushTipEnabled() const { return m_hslBrushTipEnabled; } void KisPredefinedBrushChooser::slotImportNewBrushResource() { m_itemChooser->slotButtonClicked(KisResourceItemChooser::Button_Import); } void KisPredefinedBrushChooser::slotDeleteBrushResource() { m_itemChooser->slotButtonClicked(KisResourceItemChooser::Button_Remove); } #include "moc_kis_brush_chooser.cpp" diff --git a/plugins/paintops/libpaintop/kis_brush_chooser.h b/plugins/paintops/libpaintop/kis_brush_chooser.h index d2aba86c49..2f88008645 100644 --- a/plugins/paintops/libpaintop/kis_brush_chooser.h +++ b/plugins/paintops/libpaintop/kis_brush_chooser.h @@ -1,97 +1,97 @@ /* * Copyright (c) 2004 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PREDEFINED_BRUSH_CHOOSER_H_ #define KIS_PREDEFINED_BRUSH_CHOOSER_H_ #include #include #include #include "kritapaintop_export.h" #include "ui_wdgpredefinedbrushchooser.h" class KisDoubleSliderSpinBox; class QLabel; class QCheckBox; class KisDoubleSliderSpinBox; class KisSpacingSelectionWidget; class KisCustomBrushWidget; class KisClipboardBrushWidget; class KisResourceItemChooser; class KoResource; class PAINTOP_EXPORT KisPredefinedBrushChooser : public QWidget, public Ui::WdgPredefinedBrushChooser { Q_OBJECT public: KisPredefinedBrushChooser(QWidget *parent = 0, const char *name = 0); ~KisPredefinedBrushChooser() override; KisBrushSP brush() { return m_brush; }; void setBrush(KisBrushSP brush); void setBrushSize(qreal xPixels, qreal yPixels); void setImage(KisImageWSP image); - void setHSLBrusTipEnabled(bool value); + void setHSLBrushTipEnabled(bool value); bool hslBrushTipEnabled() const; private Q_SLOTS: void slotResetBrush(); void slotResetAdjustments(); void slotSetItemSize(qreal); void slotSetItemRotation(qreal); void slotSpacingChanged(); void slotOpenStampBrush(); void slotOpenClipboardBrush(); void slotImportNewBrushResource(); void slotDeleteBrushResource(); void slotNewPredefinedBrush(KoResourceSP); void updateBrushTip(KoResourceSP, bool isChangingBrushPresets = false); void slotUpdateBrushModeButtonsState(); void slotUpdateResetBrushAdjustmentsButtonState(); void slotUpdateBrushAdjustmentsState(); void slotWriteBrushMode(); void slotWriteBrushAdjustments(); Q_SIGNALS: void sigBrushChanged(); private: KisBrushSP m_brush; KisResourceItemChooser* m_itemChooser; KisImageWSP m_image; KisCustomBrushWidget* m_stampBrushWidget; KisClipboardBrushWidget* m_clipboardBrushWidget; bool m_hslBrushTipEnabled = false; }; #endif // KIS_PREDEFINED_BRUSH_CHOOSER_H_ diff --git a/plugins/paintops/libpaintop/kis_brush_option_widget.cpp b/plugins/paintops/libpaintop/kis_brush_option_widget.cpp index 0d892456f0..7c807e8700 100644 --- a/plugins/paintops/libpaintop/kis_brush_option_widget.cpp +++ b/plugins/paintops/libpaintop/kis_brush_option_widget.cpp @@ -1,117 +1,117 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2008 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_brush_option_widget.h" #include #include #include "kis_brush_selection_widget.h" #include "kis_brush.h" #include KisBrushOptionWidget::KisBrushOptionWidget() : KisPaintOpOption(KisPaintOpOption::GENERAL, true) { m_checkable = false; m_brushSelectionWidget = new KisBrushSelectionWidget(); connect(m_brushSelectionWidget, SIGNAL(sigPrecisionChanged()), SLOT(emitSettingChanged())); connect(m_brushSelectionWidget, SIGNAL(sigBrushChanged()), SLOT(brushChanged())); m_brushSelectionWidget->hide(); setConfigurationPage(m_brushSelectionWidget); m_brushOption.setBrush(brush()); setObjectName("KisBrushOptionWidget"); } KisBrushSP KisBrushOptionWidget::brush() const { return m_brushSelectionWidget->brush(); } void KisBrushOptionWidget::setAutoBrush(bool on) { m_brushSelectionWidget->setAutoBrush(on); } void KisBrushOptionWidget::setPredefinedBrushes(bool on) { m_brushSelectionWidget->setPredefinedBrushes(on); } void KisBrushOptionWidget::setCustomBrush(bool on) { m_brushSelectionWidget->setCustomBrush(on); } void KisBrushOptionWidget::setTextBrush(bool on) { m_brushSelectionWidget->setTextBrush(on); } void KisBrushOptionWidget::setImage(KisImageWSP image) { m_brushSelectionWidget->setImage(image); } void KisBrushOptionWidget::setPrecisionEnabled(bool value) { m_brushSelectionWidget->setPrecisionEnabled(value); } -void KisBrushOptionWidget::setHSLBrusTipEnabled(bool value) +void KisBrushOptionWidget::setHSLBrushTipEnabled(bool value) { - m_brushSelectionWidget->setHSLBrusTipEnabled(value); + m_brushSelectionWidget->setHSLBrushTipEnabled(value); } void KisBrushOptionWidget::writeOptionSetting(KisPropertiesConfigurationSP settings) const { m_brushSelectionWidget->writeOptionSetting(settings); m_brushOption.writeOptionSetting(settings); } void KisBrushOptionWidget::readOptionSetting(const KisPropertiesConfigurationSP setting) { m_brushSelectionWidget->readOptionSetting(setting); m_brushOption.readOptionSetting(setting, KisGlobalResourcesInterface::instance()); m_brushSelectionWidget->setCurrentBrush(m_brushOption.brush()); } void KisBrushOptionWidget::lodLimitations(KisPaintopLodLimitations *l) const { KisBrushSP brush = this->brush(); brush->lodLimitations(l); } void KisBrushOptionWidget::brushChanged() { m_brushOption.setBrush(brush()); emitSettingChanged(); } bool KisBrushOptionWidget::presetIsValid() { return m_brushSelectionWidget->presetIsValid(); } void KisBrushOptionWidget::hideOptions(const QStringList &options) { m_brushSelectionWidget->hideOptions(options); } #include "moc_kis_brush_option_widget.cpp" diff --git a/plugins/paintops/libpaintop/kis_brush_option_widget.h b/plugins/paintops/libpaintop/kis_brush_option_widget.h index c645d388a9..1922d7d74a 100644 --- a/plugins/paintops/libpaintop/kis_brush_option_widget.h +++ b/plugins/paintops/libpaintop/kis_brush_option_widget.h @@ -1,75 +1,75 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2008 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_BRUSH_OPTION_H #define KIS_BRUSH_OPTION_H #include "kis_paintop_option.h" #include "kis_brush_option.h" #include #include "kis_brush.h" class KisBrushSelectionWidget; /** * The brush option allows the user to select a particular brush * footprint for suitable paintops */ class PAINTOP_EXPORT KisBrushOptionWidget : public KisPaintOpOption { Q_OBJECT public: KisBrushOptionWidget(); /** * @return the currently selected brush */ KisBrushSP brush() const; void setAutoBrush(bool on); void setPredefinedBrushes(bool on); void setCustomBrush(bool on); void setTextBrush(bool on); void setImage(KisImageWSP image) override; void setPrecisionEnabled(bool value); - void setHSLBrusTipEnabled(bool value); + void setHSLBrushTipEnabled(bool value); void writeOptionSetting(KisPropertiesConfigurationSP setting) const override; void readOptionSetting(const KisPropertiesConfigurationSP setting) override; void lodLimitations(KisPaintopLodLimitations *l) const override; bool presetIsValid(); void hideOptions(const QStringList &options); private Q_SLOTS: void brushChanged(); private: KisBrushSelectionWidget * m_brushSelectionWidget; KisBrushOptionProperties m_brushOption; }; #endif diff --git a/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp b/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp index 18b367b77b..e92a67406a 100644 --- a/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp +++ b/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp @@ -1,335 +1,335 @@ /* * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2014 Mohit Goyal * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_selection_widget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_brush.h" #include "kis_auto_brush.h" #include "kis_imagepipe_brush.h" #include "kis_brush_chooser.h" #include "kis_auto_brush_widget.h" #include "kis_custom_brush_widget.h" #include "kis_clipboard_brush_widget.h" #include "kis_text_brush_chooser.h" KisBrushSelectionWidget::KisBrushSelectionWidget(QWidget * parent) : QWidget(parent), m_currentBrushWidget(0) { uiWdgBrushChooser.setupUi(this); m_buttonGroup = new QButtonGroup(this); m_buttonGroup->setExclusive(true); m_layout = new QGridLayout(uiWdgBrushChooser.settingsFrame); m_autoBrushWidget = new KisAutoBrushWidget(this, "autobrush"); connect(m_autoBrushWidget, SIGNAL(sigBrushChanged()), SIGNAL(sigBrushChanged())); addChooser(i18n("Auto"), m_autoBrushWidget, AUTOBRUSH, KoGroupButton::GroupLeft); m_predefinedBrushWidget = new KisPredefinedBrushChooser(this); connect(m_predefinedBrushWidget, SIGNAL(sigBrushChanged()), SIGNAL(sigBrushChanged())); addChooser(i18n("Predefined"), m_predefinedBrushWidget, PREDEFINEDBRUSH, KoGroupButton::GroupCenter); m_textBrushWidget = new KisTextBrushChooser(this, "textbrush", i18n("Text")); connect(m_textBrushWidget, SIGNAL(sigBrushChanged()), SIGNAL(sigBrushChanged())); addChooser(i18n("Text"), m_textBrushWidget, TEXTBRUSH, KoGroupButton::GroupRight); connect(m_buttonGroup, SIGNAL(buttonClicked(int)), this, SLOT(buttonClicked(int))); Q_FOREACH (QWidget * widget, m_chooserMap.values()) { m_mininmumSize = m_mininmumSize.expandedTo(widget->sizeHint()); } setCurrentWidget(m_autoBrushWidget); uiWdgBrushChooser.sliderPrecision->setRange(1, 5); uiWdgBrushChooser.sliderPrecision->setSingleStep(1); uiWdgBrushChooser.sliderPrecision->setPageStep(1); connect(uiWdgBrushChooser.sliderPrecision, SIGNAL(valueChanged(int)), SLOT(precisionChanged(int))); connect(uiWdgBrushChooser.autoPrecisionCheckBox, SIGNAL(stateChanged(int)), SLOT(setAutoPrecisionEnabled(int))); uiWdgBrushChooser.sliderPrecision->setValue(5); setPrecisionEnabled(false); m_presetIsValid = true; } KisBrushSelectionWidget::~KisBrushSelectionWidget() { } KisBrushSP KisBrushSelectionWidget::brush() const { KisBrushSP theBrush; switch (m_buttonGroup->checkedId()) { case AUTOBRUSH: theBrush = m_autoBrushWidget->brush(); break; case PREDEFINEDBRUSH: theBrush = m_predefinedBrushWidget->brush(); break; case TEXTBRUSH: theBrush = m_textBrushWidget->brush(); break; default: ; } // Fallback to auto brush if no brush selected // Can happen if there is no predefined brush found if (!theBrush) theBrush = m_autoBrushWidget->brush(); return theBrush; } void KisBrushSelectionWidget::setAutoBrush(bool on) { m_buttonGroup->button(AUTOBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setPredefinedBrushes(bool on) { m_buttonGroup->button(PREDEFINEDBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setCustomBrush(bool on) { m_buttonGroup->button(CUSTOMBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setClipboardBrush(bool on) { m_buttonGroup->button(CLIPBOARDBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setTextBrush(bool on) { m_buttonGroup->button(TEXTBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setImage(KisImageWSP image) { m_predefinedBrushWidget->setImage(image); } void KisBrushSelectionWidget::setCurrentBrush(KisBrushSP brush) { if (!brush) { return; } // XXX: clever code have brush plugins know their configuration // pane, so we don't have to have this if statement and // have an extensible set of brush types if (dynamic_cast(brush.data())) { setCurrentWidget(m_autoBrushWidget); m_autoBrushWidget->setBrush(brush); } else if (dynamic_cast(brush.data())) { setCurrentWidget(m_textBrushWidget); m_textBrushWidget->setBrush(brush); } else { setCurrentWidget(m_predefinedBrushWidget); m_predefinedBrushWidget->setBrush(brush); } } void KisBrushSelectionWidget::buttonClicked(int id) { setCurrentWidget(m_chooserMap[id]); emit sigBrushChanged(); } void KisBrushSelectionWidget::precisionChanged(int value) { QString toolTip; switch (value) { case 1: toolTip = i18n("Precision Level 1 (fastest)\n" "Subpixel precision: disabled\n" "Brush size precision: 5%\n" "\n" "Optimal for very big brushes"); break; case 2: toolTip = i18n("Precision Level 2\n" "Subpixel precision: disabled\n" "Brush size precision: 1%\n" "\n" "Optimal for big brushes"); break; case 3: toolTip = i18n("Precision Level 3\n" "Subpixel precision: disabled\n" "Brush size precision: exact"); break; case 4: toolTip = i18n("Precision Level 4 (optimal)\n" "Subpixel precision: 50%\n" "Brush size precision: exact\n" "\n" "Gives up to 50% better performance in comparison to Level 5"); break; case 5: toolTip = i18n("Precision Level 5 (best quality)\n" "Subpixel precision: exact\n" "Brush size precision: exact\n" "\n" "The slowest performance. Best quality."); break; } uiWdgBrushChooser.sliderPrecision->blockSignals(true); uiWdgBrushChooser.sliderPrecision->setValue(value); uiWdgBrushChooser.sliderPrecision->blockSignals(false); uiWdgBrushChooser.sliderPrecision->setToolTip(toolTip); m_precisionOption.setPrecisionLevel(value); emit sigPrecisionChanged(); } void KisBrushSelectionWidget::writeOptionSetting(KisPropertiesConfigurationSP settings) const { m_precisionOption.writeOptionSetting(settings); } void KisBrushSelectionWidget::readOptionSetting(const KisPropertiesConfigurationSP setting) { m_precisionOption.readOptionSetting(setting); uiWdgBrushChooser.sliderPrecision->setValue(m_precisionOption.precisionLevel()); uiWdgBrushChooser.autoPrecisionCheckBox->setChecked(m_precisionOption.autoPrecisionEnabled()); } void KisBrushSelectionWidget::setPrecisionEnabled(bool value) { uiWdgBrushChooser.autoPrecisionCheckBox->setVisible(value); uiWdgBrushChooser.sliderPrecision->setVisible(value); uiWdgBrushChooser.lblPrecision->setVisible(value); } void KisBrushSelectionWidget::hideOptions(const QStringList &options) { Q_FOREACH(const QString &option, options) { QStringList l = option.split("/"); if (l.count() != 2) { continue; } QObject *o = 0; if (l[0] == "KisAutoBrushWidget") { o = m_autoBrushWidget->findChild(l[1]); } else if (l[0] == "KisBrushChooser") { o = m_predefinedBrushWidget->findChild(l[1]); } else if (l[0] == "KisTextBrushChooser") { o = m_textBrushWidget->findChild(l[1]); } else { qWarning() << "KisBrushSelectionWidget: Invalid option given to disable:" << option; } if (o) { QWidget *w = qobject_cast(o); if (w) { w->setVisible(false); } o = 0; } } } -void KisBrushSelectionWidget::setHSLBrusTipEnabled(bool value) +void KisBrushSelectionWidget::setHSLBrushTipEnabled(bool value) { - m_predefinedBrushWidget->setHSLBrusTipEnabled(value); + m_predefinedBrushWidget->setHSLBrushTipEnabled(value); } bool KisBrushSelectionWidget::hslBrushTipEnabled() const { return m_predefinedBrushWidget->hslBrushTipEnabled(); } void KisBrushSelectionWidget::setCurrentWidget(QWidget* widget) { if (widget == m_currentBrushWidget) return; if (m_currentBrushWidget) { m_layout->removeWidget(m_currentBrushWidget); m_currentBrushWidget->setParent(this); m_currentBrushWidget->hide(); } widget->setMinimumSize(m_mininmumSize); m_currentBrushWidget = widget; m_layout->addWidget(widget); m_currentBrushWidget->show(); m_buttonGroup->button(m_chooserMap.key(widget))->setChecked(true); m_presetIsValid = (m_buttonGroup->checkedId() != CUSTOMBRUSH); } void KisBrushSelectionWidget::addChooser(const QString& text, QWidget* widget, int id, KoGroupButton::GroupPosition pos) { KoGroupButton * button = new KoGroupButton(this); button->setGroupPosition(pos); button->setText(text); button->setAutoRaise(false); button->setCheckable(true); uiWdgBrushChooser.brushChooserButtonLayout->addWidget(button); m_buttonGroup->addButton(button, id); m_chooserMap[m_buttonGroup->id(button)] = widget; widget->hide(); } void KisBrushSelectionWidget::setAutoPrecisionEnabled(int value) { m_precisionOption.setAutoPrecisionEnabled(value); if (m_precisionOption.autoPrecisionEnabled()) { precisionChanged(m_precisionOption.precisionLevel()); uiWdgBrushChooser.sliderPrecision->setEnabled(false); uiWdgBrushChooser.lblPrecision->setEnabled(false); } else { uiWdgBrushChooser.sliderPrecision->setEnabled(true); uiWdgBrushChooser.lblPrecision->setEnabled(true); } emit sigPrecisionChanged(); } #include "moc_kis_brush_selection_widget.cpp" diff --git a/plugins/paintops/libpaintop/kis_brush_selection_widget.h b/plugins/paintops/libpaintop/kis_brush_selection_widget.h index ce02905fac..d4b11bff33 100644 --- a/plugins/paintops/libpaintop/kis_brush_selection_widget.h +++ b/plugins/paintops/libpaintop/kis_brush_selection_widget.h @@ -1,119 +1,119 @@ /* * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2014 Mohit Goyal * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_BRUSH_SELECTION_WIDGET_H #define KIS_BRUSH_SELECTION_WIDGET_H #include #include #include #include #include "kis_precision_option.h" #include "ui_wdgbrushchooser.h" class KisAutoBrushWidget; class KisPredefinedBrushChooser; class KisTextBrushChooser; class KisCustomBrushWidget; class KisClipboardBrushWidget; class KisBrush; /** * Compound widget that collects all the various brush selection widgets. */ class PAINTOP_EXPORT KisBrushSelectionWidget : public QWidget { Q_OBJECT public: KisBrushSelectionWidget(QWidget * parent = 0); ~KisBrushSelectionWidget() override; KisBrushSP brush() const; void setAutoBrush(bool on); void setPredefinedBrushes(bool on); void setCustomBrush(bool on); void setClipboardBrush(bool on); void setTextBrush(bool on); void setImage(KisImageWSP image); void setCurrentBrush(KisBrushSP brush); bool presetIsValid() { return m_presetIsValid; } void writeOptionSetting(KisPropertiesConfigurationSP settings) const; void readOptionSetting(const KisPropertiesConfigurationSP setting); void setPrecisionEnabled(bool value); bool autoPrecisionEnabled(); void hideOptions(const QStringList &options); - void setHSLBrusTipEnabled(bool value); + void setHSLBrushTipEnabled(bool value); bool hslBrushTipEnabled() const; Q_SIGNALS: void sigBrushChanged(); void sigPrecisionChanged(); private Q_SLOTS: void buttonClicked(int id); void precisionChanged(int value); void setAutoPrecisionEnabled(int value); private: void setCurrentWidget(QWidget * widget); void addChooser(const QString & text, QWidget * widget, int id, KoGroupButton::GroupPosition pos); private: enum Type { AUTOBRUSH, PREDEFINEDBRUSH, CUSTOMBRUSH, TEXTBRUSH, CLIPBOARDBRUSH }; bool m_presetIsValid; Ui_WdgBrushChooser uiWdgBrushChooser; QGridLayout * m_layout; QWidget * m_currentBrushWidget; QHash m_chooserMap; QButtonGroup * m_buttonGroup; QSize m_mininmumSize; KisAutoBrushWidget * m_autoBrushWidget; KisPredefinedBrushChooser * m_predefinedBrushWidget; KisTextBrushChooser * m_textBrushWidget; KisPrecisionOption m_precisionOption; }; #endif