diff --git a/krita/data/templates/animation/.directory b/krita/data/templates/animation/.directory index 2faab82dfc..351b8d2e3f 100644 --- a/krita/data/templates/animation/.directory +++ b/krita/data/templates/animation/.directory @@ -1,16 +1,17 @@ [Desktop Entry] Name=Animation Templates Name[ca]=Plantilles d'animació Name[ca@valencia]=Plantilles d'animació Name[cs]=Šablony animací: +Name[de]=Animations-Vorlagen Name[en_GB]=Animation Templates Name[es]=Plantillas de animación Name[it]=Modelli di animazioni Name[nl]=Animatiesjablonen Name[pl]=Szablony animacji Name[pt]=Modelos de Animações Name[pt_BR]=Modelos de animação Name[sv]=Animeringsmallar Name[uk]=Шаблони анімацій Name[x-test]=xxAnimation Templatesxx X-KDE-DefaultTab=true diff --git a/krita/krita.action b/krita/krita.action index fe025f8224..7d8c9a6cca 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -1,2948 +1,2948 @@ General Open Resources Folder Opens a file browser at the location Krita saves resources such as brushes to. Opens a file browser at the location Krita saves resources such as brushes to. Open Resources Folder 0 0 false Cleanup removed files... Cleanup removed files Cleanup removed files 0 0 false C&ascade Cascade Cascade 10 0 false &Tile Tile Tile 10 0 false Create Resource Bundle... Create Resource Bundle Create Resource Bundle 0 0 false Show File Toolbar Show File Toolbar Show File Toolbar false Show color selector Show color selector Show color selector Shift+I false Show MyPaint shade selector Show MyPaint shade selector Show MyPaint shade selector Shift+M false Show minimal shade selector Show minimal shade selector Show minimal shade selector Shift+N false Show color history Show color history Show color history H false Show common colors Show common colors Show common colors U false Show Tool Options Show Tool Options Show Tool Options \ false Show Brush Editor Show Brush Editor Show Brush Editor F5 false Show Brush Presets Show Brush Presets Show Brush Presets F6 false Toggle Tablet Debugger Toggle Tablet Debugger Toggle Tablet Debugger 0 0 Ctrl+Shift+T false Rename Composition... Rename Composition Rename Composition 0 0 false Update Composition Update Composition Update Composition 0 0 false Painting Make brush color lighter Make brush color lighter Make brush color lighter 0 0 L false Make brush color darker Make brush color darker Make brush color darker 0 0 K false Make brush color more saturated Make brush color more saturated Make brush color more saturated false Make brush color more desaturated Make brush color more desaturated Make brush color more desaturated false Shift brush color hue clockwise Shift brush color hue clockwise Shift brush color hue clockwise false Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise false Make brush color more red Make brush color more red Make brush color more red false Make brush color more green Make brush color more green Make brush color more green false Make brush color more blue Make brush color more blue Make brush color more blue false Make brush color more yellow Make brush color more yellow Make brush color more yellow false Increase opacity Increase opacity Increase opacity 0 0 O false Decrease opacity Decrease opacity Decrease opacity 0 0 I false draw-eraser Set eraser mode Set eraser mode Set eraser mode 10000 0 E true view-refresh Reload Original Preset Reload Original Preset Reload Original Preset 10000 false transparency-unlocked Preserve Alpha Preserve Alpha Preserve Alpha 10000 true transform_icons_penPressure Use Pen Pressure Use Pen Pressure Use Pen Pressure 10000 true symmetry-horizontal Horizontal Mirror Tool Horizontal Mirror Tool Horizontal Mirror Tool 10000 true symmetry-vertical Vertical Mirror Tool Vertical Mirror Tool Vertical Mirror Tool 10000 true Paste at cursor Paste at cursor Paste at cursor 0 0 false &Invert Selection Invert current selection Invert Selection 10000000000 100 Ctrl+Shift+I false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false &Toggle Selection Display Mode Toggle Selection Display Mode Toggle Selection Display Mode 0 0 false Next Favourite Preset Next Favourite Preset Next Favourite Preset , false Previous Favourite Preset Previous Favourite Preset Previous Favourite Preset . false preset-switcher Switch to Previous Preset Switch to Previous Preset Switch to Previous Preset / false Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar true Reset Foreground and Background Color Reset Foreground and Background Color Reset Foreground and Background Color D false Swap Foreground and Background Color Swap Foreground and Background Color Swap Foreground and Background Color X false smoothing-weighted Brush Smoothing: Weighted Brush Smoothing: Weighted Brush Smoothing: Weighted false smoothing-no Brush Smoothing: Disabled Brush Smoothing: Disabled Brush Smoothing: Disabled false smoothing-stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer false Decrease Brush Size Decrease Brush Size Decrease Brush Size 0 0 [ false smoothing-basic Brush Smoothing: Basic Brush Smoothing: Basic Brush Smoothing: Basic false Increase Brush Size Increase Brush Size Increase Brush Size 0 0 ] false Toggle Assistant Toggle Assistant ToggleAssistant Ctrl+Shift+L true Undo Polygon Selection Points Undo Polygon Selection Points Undo Polygon Selection Points Shift+Z false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false Convert &to Shape Convert to Shape Convert to Shape 10000000000 0 false &Select Opaque Select Opaque Select Opaque 100000 100 false &Show Global Selection Mask Shows global selection as a usual selection mask in <interface>Layers</interface> docker Show Global Selection Mask 100000 100 true Filters color-to-alpha &Color to Alpha... Color to Alpha Color to Alpha 10000 0 false &Top Edge Detection Top Edge Detection Top Edge Detection 10000 0 false &Index Colors... Index Colors Index Colors 10000 0 false Emboss Horizontal &Only Emboss Horizontal Only Emboss Horizontal Only 10000 0 false D&odge Dodge Dodge 10000 0 false &Sharpen Sharpen Sharpen 10000 0 false B&urn Burn Burn 10000 0 false &Mean Removal Mean Removal Mean Removal 10000 0 false &Gaussian Blur... Gaussian Blur Gaussian Blur 10000 0 false Emboss &in All Directions Emboss in All Directions Emboss in All Directions 10000 0 false &Small Tiles... Small Tiles Small Tiles 10000 0 false &Levels... Levels Levels 10000 0 Ctrl+L false &Sobel... Sobel Sobel 10000 0 false &Wave... Wave Wave 10000 0 false &Motion Blur... Motion Blur Motion Blur 10000 0 false &Color Adjustment curves... Color Adjustment curves Color Adjustment curves 10000 0 Ctrl+M false Pi&xelize... Pixelize Pixelize 10000 0 false Emboss (&Laplacian) Emboss (Laplacian) Emboss (Laplacian) 10000 0 false &Left Edge Detection Left Edge Detection Left Edge Detection 10000 0 false &Blur... Blur Blur 10000 0 false &Raindrops... Raindrops Raindrops 10000 0 false &Bottom Edge Detection Bottom Edge Detection Bottom Edge Detection 10000 0 false &Random Noise... Random Noise Random Noise 10000 0 false &Brightness/Contrast curve... Brightness/Contrast curve Brightness/Contrast curve 10000 0 false Colo&r Balance.. Color Balance.. Color Balance.. 10000 0 Ctrl+B false &Phong Bumpmap... Phong Bumpmap Phong Bumpmap 10000 0 false &Desaturate Desaturate Desaturate 10000 0 Ctrl+Shift+U false Color &Transfer... Color Transfer Color Transfer 10000 0 false Emboss &Vertical Only Emboss Vertical Only Emboss Vertical Only 10000 0 false &Lens Blur... Lens Blur Lens Blur 10000 0 false M&inimize Channel Minimize Channel Minimize Channel 10000 0 false M&aximize Channel Maximize Channel Maximize Channel 10000 0 false &Oilpaint... Oilpaint Oilpaint 10000 0 false &Right Edge Detection Right Edge Detection Right Edge Detection 10000 0 false &Auto Contrast Auto Contrast Auto Contrast 10000 0 false &Round Corners... Round Corners Round Corners 10000 0 false &Unsharp Mask... Unsharp Mask Unsharp Mask 10000 0 false &Emboss with Variable Depth... Emboss with Variable Depth Emboss with Variable Depth 10000 0 false Emboss &Horizontal && Vertical Emboss Horizontal & Vertical Emboss Horizontal & Vertical 10000 0 false Random &Pick... Random Pick Random Pick 10000 0 false &Gaussian Noise Reduction... Gaussian Noise Reduction Gaussian Noise Reduction 10000 0 false &Posterize... Posterize Posterize 10000 0 false &Wavelet Noise Reducer... Wavelet Noise Reducer Wavelet Noise Reducer 10000 0 false &HSV Adjustment... HSV Adjustment HSV Adjustment 10000 0 Ctrl+U false Tool Shortcuts Dynamic Brush Tool Dynamic Brush Tool Dynamic Brush Tool false Crop Tool Crop the image to an area Crop the image to an area C false Polygon Tool Polygon Tool. Shift-mouseclick ends the polygon. Polygon Tool. Shift-mouseclick ends the polygon. false References References References false Rectangle Tool Rectangle Tool Rectangle Tool false Multibrush Tool Multibrush Tool Multibrush Tool Q false Shape Manipulation Tool Shape Manipulation Tool Shape Manipulation Tool false Color Picker Select a color from the image or current layer Select a color from the image or current layer P false Text Editing Tool Text editing Text editing false Outline Selection Tool Outline Selection Tool Outline Selection Tool false Artistic Text Tool Artistic text editing Artistic text editing false Bezier Curve Selection Tool Select a Bezier Curve Selection Tool false Similar Color Selection Tool Select a Similar Color Selection Tool false Fill Tool Fill a contiguous area of color with a color, or fill a selection. Fill a contiguous area of color with a color, or fill a selection. F false Line Tool Line Tool Line Tool false Freehand Path Tool Freehand Path Tool Freehand Path Tool false Bezier Curve Tool Bezier Curve Tool. Shift-mouseclick ends the curve. Bezier Curve Tool. Shift-mouseclick ends the curve. false Ellipse Tool Ellipse Tool Ellipse Tool false Freehand Brush Tool Freehand Brush Tool Freehand Brush Tool B false Create object Create object Create object false Elliptical Selection Tool Elliptical Selection Tool Elliptical Selection Tool J false Contiguous Selection Tool Contiguous Selection Tool Contiguous Selection Tool false Pattern editing Pattern editing Pattern editing false Review Review Review false Draw a gradient. Draw a gradient. Draw a gradient. G false Polygonal Selection Tool Polygonal Selection Tool Polygonal Selection Tool false Measurement Tool Measure the distance between two points Measure the distance between two points false Rectangular Selection Tool Rectangular Selection Tool Rectangular Selection Tool Ctrl+R false Move Tool Move a layer Move a layer T false Vector Image Tool Vector Image (EMF/WMF/SVM/SVG) tool Vector Image (EMF/WMF/SVM/SVG) tool false Calligraphy Calligraphy Calligraphy false Path editing Path editing Path editing false Polyline Tool Polyline Tool. Shift-mouseclick ends the polyline. Polyline Tool. Shift-mouseclick ends the polyline. false Transform Tool Transform a layer or a selection Transform a layer or a selection Ctrl+T false Ruler assistant editor tool Ruler assistant editor tool Ruler assistant editor tool false Text tool Text tool Text tool false Gradient Editing Tool Gradient editing Gradient editing false Blending Modes Select Normal Blending Mode Select Normal Blending Mode Select Normal Blending Mode 0 0 Alt+Shift+N false Select Dissolve Blending Mode Select Dissolve Blending Mode Select Dissolve Blending Mode 0 0 Alt+Shift+I false Select Behind Blending Mode Select Behind Blending Mode Select Behind Blending Mode 0 0 Alt+Shift+Q false Select Clear Blending Mode Select Clear Blending Mode Select Clear Blending Mode 0 0 Alt+Shift+R false Select Darken Blending Mode Select Darken Blending Mode Select Darken Blending Mode 0 0 Alt+Shift+K false Select Multiply Blending Mode Select Multiply Blending Mode Select Multiply Blending Mode 0 0 Alt+Shift+M false Select Color Burn Blending Mode Select Color Burn Blending Mode Select Color Burn Blending Mode 0 0 Alt+Shift+B false Select Linear Burn Blending Mode Select Linear Burn Blending Mode Select Linear Burn Blending Mode 0 0 Alt+Shift+A false Select Lighten Blending Mode Select Lighten Blending Mode Select Lighten Blending Mode 0 0 Alt+Shift+G false Select Screen Blending Mode Select Screen Blending Mode Select Screen Blending Mode 0 0 Alt+Shift+S false Select Color Dodge Blending Mode Select Color Dodge Blending Mode Select Color Dodge Blending Mode 0 0 Alt+Shift+D false Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode 0 0 Alt+Shift+W false Select Overlay Blending Mode Select Overlay Blending Mode Select Overlay Blending Mode 0 0 Alt+Shift+O false Select Soft Light Blending Mode Select Soft Light Blending Mode Select Soft Light Blending Mode 0 0 Alt+Shift+F false Select Hard Light Blending Mode Select Hard Light Blending Mode Select Hard Light Blending Mode 0 0 Alt+Shift+H false Select Vivid Light Blending Mode Select Vivid Light Blending Mode Select Vivid Light Blending Mode 0 0 Alt+Shift+V false Select Linear Light Blending Mode Select Linear Light Blending Mode Select Linear Light Blending Mode 0 0 Alt+Shift+J false Select Pin Light Blending Mode Select Pin Light Blending Mode Select Pin Light Blending Mode 0 0 Alt+Shift+Z false Select Hard Mix Blending Mode Select Hard Mix Blending Mode Select Hard Mix Blending Mode 0 0 Alt+Shift+L false Select Difference Blending Mode Select Difference Blending Mode Select Difference Blending Mode 0 0 Alt+Shift+E false Select Exclusion Blending Mode Select Exclusion Blending Mode Select Exclusion Blending Mode 0 0 Alt+Shift+X false Select Hue Blending Mode Select Hue Blending Mode Select Hue Blending Mode 0 0 Alt+Shift+U false Select Saturation Blending Mode Select Saturation Blending Mode Select Saturation Blending Mode 0 0 Alt+Shift+T false Select Color Blending Mode Select Color Blending Mode Select Color Blending Mode 0 0 Alt+Shift+C false Select Luminosity Blending Mode Select Luminosity Blending Mode Select Luminosity Blending Mode 0 0 Alt+Shift+Y false Animation Previous frame Move to previous frame Move to previous frame 1 0 false Next frame Move to next frame Move to next frame 1 0 false Play / pause animation Play / pause animation Play / pause animation 1 0 false Add blank frame Add blank frame Add blank frame 100000 0 false Copy Frame Add duplicate frame Add duplicate frame 100000 0 false Toggle onion skin Toggle onion skin Toggle onion skin 100000 0 false Previous Keyframe false Next Keyframe false First Frame false Last Frame false Auto Frame Mode true true Add blank frame Add blank frame Add blank frame 100000 0 false Show in Timeline true Layers Activate next layer Activate next layer Activate next layer 1000 0 PgUp false Activate previous layer Activate previous layer Activate previous layer 1000 0 PgDown false Activate previously selected layer Activate previously selected layer Activate previously selected layer 1000 0 ; false groupLayer &Group Layer Group Layer Group Layer 1000 0 false cloneLayer &Clone Layer Clone Layer Clone Layer 1000 0 false vectorLayer &Vector Layer Vector Layer Vector Layer 1000 0 false filterLayer &Filter Layer... Filter Layer Filter Layer 1000 0 false fillLayer &Fill Layer... Fill Layer Fill Layer 1000 0 false fileLayer &File Layer... File Layer File Layer 1000 0 false transparencyMask &Transparency Mask Transparency Mask Transparency Mask 100000 0 false filterMask &Filter Mask... Filter Mask Filter Mask 100000 0 false filterMask &Colorize Mask Colorize Mask Colorize Mask 100000 0 false transformMask &Transform Mask... Transform Mask Transform Mask 100000 0 false selectionMask &Local Selection Local Selection Local Selection 100000 0 false view-filter &Isolate Layer Isolate Layer Isolate Layer 1000 0 true paintLayer &Paint Layer Paint Layer Paint Layer 1000 0 Insert false &New Layer From Visible New layer from visible New layer from visible 1000 0 false duplicatelayer &Duplicate Layer or Mask Duplicate Layer or Mask Duplicate Layer or Mask 1000 0 Ctrl+J false &Cut Selection to New Layer Cut Selection to New Layer Cut Selection to New Layer 100000000 1 Ctrl+Shift+J false Copy &Selection to New Layer Copy Selection to New Layer Copy Selection to New Layer 100000000 0 Ctrl+Alt+J false Copy Layer Copy layer to clipboard Copy layer to clipboard 10000 0 false Cut Layer Cut layer to clipboard Cut layer to clipboard 10000 0 false Paste Layer Paste layer from clipboard Paste layer from clipboard 10000 0 false Quick Group Create a group layer containing selected layers Quick Group 100000 0 Ctrl+G false Quick Ungroup Remove grouping of the layers or remove one layer out of the group Quick Ungroup 100000 0 Ctrl+Alt+G false Quick Clipping Group Group selected layers and add a layer with clipped alpha channel Quick Clipping Group 100000 0 Ctrl+Shift+G false All Layers Select all layers Select all layers 10000 0 false Visible Layers Select all visible layers Select all visible layers 10000 0 false Locked Layers Select all locked layers Select all locked layers 10000 0 false Invisible Layers Select all invisible layers Select all invisible layers 10000 0 false Unlocked Layers Select all unlocked layers Select all unlocked layers 10000 0 false document-save &Save Layer/Mask... Save Layer/Mask Save Layer/Mask 1000 0 false document-save Save &Group Layers... Save Group Layers Save Group Layers 100000 0 false Convert group to &animated layer Convert child layers into animation frames Convert child layers into animation frames 100000 0 false I&mport Layer... Import Layer Import Layer 100000 0 false paintLayer &as Paint Layer... as Paint Layer as Paint Layer 1000 0 false transparencyMask as &Transparency Mask... as Transparency Mask as Transparency Mask 1000 0 false filterMask as &Filter Mask... as Filter Mask as Filter Mask 1000 0 false selectionMask as &Selection Mask... as Selection Mask as Selection Mask 1000 0 false paintLayer to &Paint Layer to Paint Layer to Paint Layer 1000 0 false transparencyMask to &Transparency Mask to Transparency Mask to Transparency Mask 1000 0 false filterMask to &Filter Mask... to Filter Mask to Filter Mask 1000 0 false selectionMask to &Selection Mask to Selection Mask to Selection Mask 1000 0 false transparencyMask &Alpha into Mask Alpha into Mask Alpha into Mask 100000 10 false transparency-enabled &Write as Alpha Write as Alpha Write as Alpha 1000000 1 false document-save &Save Merged... Save Merged Save Merged 1000000 0 false split-layer Split Layer... Split Layer Split Layer 1000 0 false Wavelet Decompose ... Wavelet Decompose Wavelet Decompose 1000 1 false symmetry-horizontal Mirror Layer Hori&zontally Mirror Layer Horizontally Mirror Layer Horizontally 1000 1 false symmetry-vertical Mirror Layer &Vertically Mirror Layer Vertically Mirror Layer Vertically 1000 1 false &Rotate Layer... Rotate Layer Rotate Layer - 100000 + 1000 1 false object-rotate-right Rotate &Layer 90° to the Right Rotate Layer 90° to the Right Rotate Layer 90° to the Right - 100000 + 1000 1 false object-rotate-left Rotate Layer &90° to the Left Rotate Layer 90° to the Left Rotate Layer 90° to the Left - 100000 + 1000 1 false Rotate Layer &180° Rotate Layer 180° Rotate Layer 180° - 100000 + 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 - 100000 + 1000 1 false &Offset Layer... Offset Layer Offset Layer 100000 1 false Clones &Array... Clones Array Clones Array 100000 0 false &Edit metadata... Edit metadata Edit metadata 100000 1 false &Histogram... Histogram Histogram 100000 0 false &Convert Layer Color Space... Convert Layer Color Space Convert Layer Color Space 100000 1 false merge-layer-below &Merge with Layer Below Merge with Layer Below Merge with Layer Below 100000 0 Ctrl+E false &Flatten Layer Flatten Layer Flatten Layer 100000 0 false Ras&terize Layer Rasterize Layer Rasterize Layer 10000000 1 false Flatten ima&ge Flatten image Flatten image 100000 0 Ctrl+Shift+E false La&yer Style... Layer Style Layer Style 100000 1 false Move into previous group Move into previous group Move into previous group 0 0 false Move into next group Move into next group Move into next group 0 0 false Rename current layer Rename current layer Rename current layer 100000 0 F2 false deletelayer &Remove Layer Remove Layer Remove Layer 1000 1 Shift+Delete false arrowupblr Move Layer or Mask Up Move Layer or Mask Up Ctrl+PgUp false arrowdown Move Layer or Mask Down Move Layer or Mask Down Ctrl+PgDown false properties &Properties... Properties Properties 1000 1 F3 false diff --git a/krita/main.cc b/krita/main.cc index 3729e98e9f..0f3df58f80 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,277 +1,278 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "data/splash/splash_screen.xpm" #include "data/splash/splash_holidays.xpm" #include "KisDocument.h" #include "kis_splash_screen.h" #include "KisPart.h" #include "KisApplicationArguments.h" #include #if defined Q_OS_WIN #include #include #include #elif defined HAVE_X11 #include #include #endif #if defined HAVE_KCRASH #include #elif defined USE_DRMINGW namespace { void tryInitDrMingw() { wchar_t path[MAX_PATH]; QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll"); if (pathStr.size() > MAX_PATH - 1) { return; } int pathLen = pathStr.toWCharArray(path); path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator HMODULE hMod = LoadLibraryW(path); if (!hMod) { return; } // No need to call ExcHndlInit since the crash handler is installed on DllMain auto myExcHndlSetLogFileNameA = reinterpret_cast(GetProcAddress(hMod, "ExcHndlSetLogFileNameA")); if (!myExcHndlSetLogFileNameA) { return; } // Set the log file path to %LocalAppData%\kritacrash.log QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QStringLiteral("\\kritacrash.log"); myExcHndlSetLogFileNameA(logFile.toLocal8Bit()); } } // namespace #endif extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty(); #if defined HAVE_X11 qputenv("QT_QPA_PLATFORM", "xcb"); #endif /** * Disable debug output by default. (krita.input enables tablet debugging.) * Debug logs can be controlled by an environment variable QT_LOGGING_RULES. * * As an example, to get full debug output, run the following: * export QT_LOGGING_RULES="krita*=true"; krita * * See: http://doc.qt.io/qt-5/qloggingcategory.html */ QLoggingCategory::setFilterRules("krita*.debug=false\n" "krita*.warning=true\n" "krita.tabletlog=true"); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita3" + QDesktopServices::storageLocation(QDesktopServices::HomeLocation).replace("/", "_"); key = key.replace(":", "_").replace("\\","_"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); KisOpenGL::setDefaultFormat(); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); - + bool singleApplication = true; #if QT_VERSION >= 0x050600 { - QSettings kritarc(configPath + QStringLiteral("/kritaopenglrc"), QSettings::IniFormat); + QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); + singleApplication = kritarc.value("EnableSingleApplication").toBool(); if (kritarc.value("EnableHiDPI", false).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } } #endif KLocalizedString::setApplicationDomain("krita"); // first create the application so we can create a pixmap KisApplication app(key, argc, argv); #ifdef Q_OS_LINUX qputenv("XDG_DATA_DIRS", QFile::encodeName(KoResourcePaths::getApplicationRoot() + "share") + ":" + qgetenv("XDG_DATA_DIRS")); #else qputenv("XDG_DATA_DIRS", QFile::encodeName(KoResourcePaths::getApplicationRoot() + "share")); #endif qDebug() << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS"); qDebug() << "Available translations" << KLocalizedString::availableApplicationTranslations(); qDebug() << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); // Now that the paths are set, set the language. First check the override from the langage // selection dialog. { QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat); languageoverride.beginGroup(QStringLiteral("Language")); QString language = languageoverride.value(qAppName(), "").toString(); qDebug() << "Override language:" << language; if (!language.isEmpty()) { KLocalizedString::setLanguages(language.split(":")); // And override Qt's locale, too qputenv("LANG", language.split(":").first().toUtf8()); QLocale locale(language.split(":").first()); QLocale::setDefault(locale); qDebug() << "Qt ui languages" << locale.uiLanguages(); } else { // And if there isn't one, check the one set by the system. // XXX: This doesn't work, for some !@#$% reason. QLocale locale = QLocale::system(); if (locale.bcp47Name() != QStringLiteral("en")) { qputenv("LANG", locale.bcp47Name().toLatin1()); KLocalizedString::setLanguages(QStringList() << locale.bcp47Name()); } } } #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); qDebug() << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif // If we should clear the config, it has to be done as soon as possible after // KisApplication has been created. Otherwise the config file may have been read // and stored in a KConfig object we have no control over. app.askClearConfig(); KisApplicationArguments args(app); - if (app.isRunning()) { + if (singleApplication && app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance const bool batchRun = (args.print() || args.exportAs() || args.exportAsPdf()); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } #if defined HAVE_X11 app.installNativeEventFilter(KisXi2EventFilter::instance()); #endif // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm)); } app.setSplashScreen(splash); #if defined Q_OS_WIN KisTabletSupportWin::init(); // app.installNativeEventFilter(new KisTabletSupportWin()); #endif if (!app.start(args)) { return 1; } #if QT_VERSION >= 0x050700 app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); #endif // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); int state = app.exec(); { - QSettings kritarc(configPath + QStringLiteral("/kritaopenglrc"), QSettings::IniFormat); + QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } return state; } diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index 66c6696cf5..d2b1a35094 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,152 +1,164 @@ org.kde.krita.desktop CC0-1.0 + Krita + Krita + Krita + Krita + Krita + Krita + Krita + Krita + Krita + Krita + Krita + xxKritaxx Digital Painting, Creative Freedom Pintura dixital, llibertá creativa 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 Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pittura digitale, libertà creativa Digital Painting, Creative Freedom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura Digital, Liberdade Criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda Digital målning, kreativ frihet Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 数码绘图,自由创作

Krita is the full-featured digital art studio.

Krita ye l'estudiu completu d'arte dixital.

Krita je potpuni digitalni umjetnički studio.

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

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

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

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 è uno studio d'arte digitale completo.

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

Krita is de digitale kunststudio vol mogelijkheden.

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 är den fullfjädrade digitala konststudion.

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

xxKrita is the full-featured digital art studio.xx

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.

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.

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.

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.

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.

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

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 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 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 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 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 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 ä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 — чудовий інструмент для створення концептуального живопису, коміксів, текстур для моделей та декорацій. У 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。

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 de pinzells avançats, els filtres impressionants i moltes funcionalitats útils que fan el Krita molt productiu.

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

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.

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.

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

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

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.

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.

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

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

尽情使用高级笔刷引擎,超赞的滤镜和很多手绘特性,发挥 Krita 绝佳的创造力。

https://www.krita.org/ https://krita.org/about/faq/ https://krita.org/support-us/donations/ https://docs.krita.org/Category:Tutorials http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_002.png http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_003.png http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_004.png http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_005.png foundation@krita.org KDE krita
diff --git a/krita/pics/tools/SVG/16/dark_calligraphy.svg b/krita/pics/tools/SVG/16/dark_calligraphy.svg index caa5ed8c4a..cef568c7c0 100644 --- a/krita/pics/tools/SVG/16/dark_calligraphy.svg +++ b/krita/pics/tools/SVG/16/dark_calligraphy.svg @@ -1,586 +1,160 @@ - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml - + Andrei Rudenko - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style="fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 24,-1.6e-6 22,1.9999986 30,9.999999 32,7.9999988 Z" + id="path889" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccc" /> + diff --git a/krita/pics/tools/SVG/16/dark_krita_tool_freehand.svg b/krita/pics/tools/SVG/16/dark_krita_tool_freehand.svg index 31675476e3..0a7555ca5f 100644 --- a/krita/pics/tools/SVG/16/dark_krita_tool_freehand.svg +++ b/krita/pics/tools/SVG/16/dark_krita_tool_freehand.svg @@ -1,211 +1,113 @@ - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml Andrei Rudenko - - diff --git a/krita/pics/tools/SVG/16/light_calligraphy.svg b/krita/pics/tools/SVG/16/light_calligraphy.svg index ab9de6f71b..7bb5927910 100644 --- a/krita/pics/tools/SVG/16/light_calligraphy.svg +++ b/krita/pics/tools/SVG/16/light_calligraphy.svg @@ -1,586 +1,160 @@ - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml Andrei Rudenko - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style="fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 24,-1.6e-6 22,1.9999986 30,9.999999 32,7.9999988 Z" + id="path889" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccc" /> + diff --git a/krita/pics/tools/SVG/16/light_krita_tool_freehand.svg b/krita/pics/tools/SVG/16/light_krita_tool_freehand.svg index a01b2283d0..252c7dbe60 100644 --- a/krita/pics/tools/SVG/16/light_krita_tool_freehand.svg +++ b/krita/pics/tools/SVG/16/light_krita_tool_freehand.svg @@ -1,212 +1,113 @@ - - - - - - - - - - - - - - - + id="defs4" /> image/svg+xml Andrei Rudenko - + inkscape:groupmode="layer"> - diff --git a/libs/flake/KoCanvasControllerWidget.cpp b/libs/flake/KoCanvasControllerWidget.cpp index d2164b01f8..5ab03cf7e1 100644 --- a/libs/flake/KoCanvasControllerWidget.cpp +++ b/libs/flake/KoCanvasControllerWidget.cpp @@ -1,598 +1,597 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2008-2009 Thomas Zander * Copyright (C) 2006 Peter Simonsson * Copyright (C) 2006, 2009 Thorsten Zachmann * Copyright (C) 2007-2010 Boudewijn Rempt * Copyright (C) 2007 C. Boemann * Copyright (C) 2006-2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoCanvasControllerWidget.h" #include "KoCanvasControllerWidget_p.h" #include "KoCanvasControllerWidgetViewport_p.h" #include "KoShape.h" #include "KoViewConverter.h" #include "KoCanvasBase.h" #include "KoCanvasObserverBase.h" #include "KoCanvasSupervisor.h" #include "KoToolManager_p.h" #include #include #include #include #include #include #include #include #include #include void KoCanvasControllerWidget::Private::setDocumentOffset() { // The margins scroll the canvas widget inside the viewport, not // the document. The documentOffset is meant to be the value that // the canvas must add to the update rect in its paint event, to // compensate. QPoint pt(q->horizontalScrollBar()->value(), q->verticalScrollBar()->value()); q->proxyObject->emitMoveDocumentOffset(pt); QWidget *canvasWidget = canvas->canvasWidget(); if (canvasWidget) { // If it isn't an OpenGL canvas if (qobject_cast(canvasWidget) == 0) { QPoint diff = q->documentOffset() - pt; if (q->canvasMode() == Spreadsheet && canvasWidget->layoutDirection() == Qt::RightToLeft) { canvasWidget->scroll(-diff.x(), diff.y()); } else { canvasWidget->scroll(diff.x(), diff.y()); } } } q->setDocumentOffset(pt); } void KoCanvasControllerWidget::Private::resetScrollBars() { // The scrollbar value always points at the top-left corner of the // bit of image we paint. int docH = q->documentSize().height() + q->margin(); int docW = q->documentSize().width() + q->margin(); int drawH = viewportWidget->height(); int drawW = viewportWidget->width(); QScrollBar *hScroll = q->horizontalScrollBar(); QScrollBar *vScroll = q->verticalScrollBar(); int horizontalReserve = vastScrollingFactor * drawW; int verticalReserve = vastScrollingFactor * drawH; int xMin = -horizontalReserve; int yMin = -verticalReserve; int xMax = docW - drawW + horizontalReserve; int yMax = docH - drawH + verticalReserve; hScroll->setRange(xMin, xMax); vScroll->setRange(yMin, yMax); int fontheight = QFontMetrics(q->font()).height(); vScroll->setPageStep(drawH); vScroll->setSingleStep(fontheight); hScroll->setPageStep(drawW); hScroll->setSingleStep(fontheight); } void KoCanvasControllerWidget::Private::emitPointerPositionChangedSignals(QEvent *event) { if (!canvas) return; if (!canvas->viewConverter()) return; QPoint pointerPos; QMouseEvent *mouseEvent = dynamic_cast(event); if (mouseEvent) { pointerPos = mouseEvent->pos(); } else { QTabletEvent *tabletEvent = dynamic_cast(event); if (tabletEvent) { pointerPos = tabletEvent->pos(); } } QPoint pixelPos = (pointerPos - canvas->documentOrigin()) + q->documentOffset(); QPointF documentPos = canvas->viewConverter()->viewToDocument(pixelPos); q->proxyObject->emitDocumentMousePositionChanged(documentPos); q->proxyObject->emitCanvasMousePositionChanged(pointerPos); } #include void KoCanvasControllerWidget::Private::activate() { QWidget *parent = q; while (parent->parentWidget()) { parent = parent->parentWidget(); } KoCanvasSupervisor *observerProvider = dynamic_cast(parent); if (!observerProvider) { return; } KoCanvasBase *canvas = q->canvas(); Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observer->setObservedCanvas(canvas); } } } void KoCanvasControllerWidget::Private::unsetCanvas(KoCanvasBase *canvas) { QWidget *parent = q; while (parent->parentWidget()) { parent = parent->parentWidget(); } KoCanvasSupervisor *observerProvider = dynamic_cast(parent); if (!observerProvider) { return; } Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { if (!canvas || observer->observedCanvas() == q->canvas()) { observer->unsetObservedCanvas(); } } } } //////////// KoCanvasControllerWidget::KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent) : QAbstractScrollArea(parent) , KoCanvasController(actionCollection) , d(new Private(this)) { // We need to set this as QDeclarativeView sets them a bit differnt from QAbstractScrollArea setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // And then our own Viewport d->viewportWidget = new Viewport(this); setViewport(d->viewportWidget); d->viewportWidget->setFocusPolicy(Qt::NoFocus); setFocusPolicy(Qt::NoFocus); setFrameStyle(0); //setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setAutoFillBackground(false); /* Fixes: apps starting at zero zoom. Details: Since the document is set on the mainwindow before loading commences the inial show/layout can choose to set the document to be very small, even to be zero pixels tall. Setting a sane minimum size on the widget means we no loger get rounding errors in zooming and we no longer end up with zero-zoom. Note: KoPage apps should probably startup with a sane document size; for Krita that's impossible */ setMinimumSize(QSize(50, 50)); setMouseTracking(true); connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetX())); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetY())); connect(d->viewportWidget, SIGNAL(sizeChanged()), this, SLOT(updateCanvasOffsetX())); connect(proxyObject, SIGNAL(moveDocumentOffset(const QPoint&)), d->viewportWidget, SLOT(documentOffsetMoved(const QPoint&))); } KoCanvasControllerWidget::~KoCanvasControllerWidget() { - d->unsetCanvas(0); delete d; } void KoCanvasControllerWidget::activate() { d->activate(); } void KoCanvasControllerWidget::scrollContentsBy(int dx, int dy) { Q_UNUSED(dx); Q_UNUSED(dy); d->setDocumentOffset(); } QSize KoCanvasControllerWidget::viewportSize() const { return viewport()->size(); } void KoCanvasControllerWidget::setDrawShadow(bool drawShadow) { d->viewportWidget->setDrawShadow(drawShadow); } void KoCanvasControllerWidget::resizeEvent(QResizeEvent *resizeEvent) { proxyObject->emitSizeChanged(resizeEvent->size()); // XXX: When resizing, keep the area we're looking at now in the // center of the resized view. d->resetScrollBars(); d->setDocumentOffset(); } void KoCanvasControllerWidget::setCanvas(KoCanvasBase *canvas) { Q_ASSERT(canvas); // param is not null if (d->canvas) { d->unsetCanvas(canvas); proxyObject->emitCanvasRemoved(this); canvas->setCanvasController(0); d->canvas->canvasWidget()->removeEventFilter(this); } canvas->setCanvasController(this); d->canvas = canvas; changeCanvasWidget(canvas->canvasWidget()); proxyObject->emitCanvasSet(this); QTimer::singleShot(0, this, SLOT(activate())); setPreferredCenterFractionX(0); setPreferredCenterFractionY(0); } KoCanvasBase* KoCanvasControllerWidget::canvas() const { if (d->canvas.isNull()) return 0; return d->canvas; } void KoCanvasControllerWidget::changeCanvasWidget(QWidget *widget) { if (d->viewportWidget->canvas()) { widget->setCursor(d->viewportWidget->canvas()->cursor()); d->viewportWidget->canvas()->removeEventFilter(this); } d->viewportWidget->setCanvas(widget); setFocusProxy(d->canvas->canvasWidget()); } int KoCanvasControllerWidget::visibleHeight() const { if (d->canvas == 0) return 0; QWidget *canvasWidget = canvas()->canvasWidget(); int height1; if (canvasWidget == 0) height1 = viewport()->height(); else height1 = qMin(viewport()->height(), canvasWidget->height()); int height2 = height(); return qMin(height1, height2); } int KoCanvasControllerWidget::visibleWidth() const { if (d->canvas == 0) return 0; QWidget *canvasWidget = canvas()->canvasWidget(); int width1; if (canvasWidget == 0) width1 = viewport()->width(); else width1 = qMin(viewport()->width(), canvasWidget->width()); int width2 = width(); return qMin(width1, width2); } int KoCanvasControllerWidget::canvasOffsetX() const { int offset = -horizontalScrollBar()->value(); if (d->canvas) { offset += d->canvas->canvasWidget()->x() + frameWidth(); } return offset; } int KoCanvasControllerWidget::canvasOffsetY() const { int offset = -verticalScrollBar()->value(); if (d->canvas) { offset += d->canvas->canvasWidget()->y() + frameWidth(); } return offset; } void KoCanvasControllerWidget::updateCanvasOffsetX() { proxyObject->emitCanvasOffsetXChanged(canvasOffsetX()); if (d->ignoreScrollSignals) return; setPreferredCenterFractionX((horizontalScrollBar()->value() + viewport()->width() / 2.0) / documentSize().width()); } void KoCanvasControllerWidget::updateCanvasOffsetY() { proxyObject->emitCanvasOffsetYChanged(canvasOffsetY()); if (d->ignoreScrollSignals) return; setPreferredCenterFractionY((verticalScrollBar()->value() + verticalScrollBar()->pageStep() / 2.0) / documentSize().height()); } void KoCanvasControllerWidget::ensureVisible(KoShape *shape) { Q_ASSERT(shape); ensureVisible(d->canvas->viewConverter()->documentToView(shape->boundingRect())); } void KoCanvasControllerWidget::ensureVisible(const QRectF &rect, bool smooth) { QRect currentVisible(-canvasOffsetX(), -canvasOffsetY(), visibleWidth(), visibleHeight()); QRect viewRect = rect.toRect(); viewRect.translate(d->canvas->documentOrigin()); if (!viewRect.isValid() || currentVisible.contains(viewRect)) return; // its visible. Nothing to do. // if we move, we move a little more so the amount of times we have to move is less. int jumpWidth = smooth ? 0 : currentVisible.width() / 5; int jumpHeight = smooth ? 0 : currentVisible.height() / 5; if (!smooth && viewRect.width() + jumpWidth > currentVisible.width()) jumpWidth = 0; if (!smooth && viewRect.height() + jumpHeight > currentVisible.height()) jumpHeight = 0; int horizontalMove = 0; if (currentVisible.width() <= viewRect.width()) // center view horizontalMove = viewRect.center().x() - currentVisible.center().x(); else if (currentVisible.x() > viewRect.x()) // move left horizontalMove = viewRect.x() - currentVisible.x() - jumpWidth; else if (currentVisible.right() < viewRect.right()) // move right horizontalMove = viewRect.right() - qMax(0, currentVisible.right() - jumpWidth); int verticalMove = 0; if (currentVisible.height() <= viewRect.height()) // center view verticalMove = viewRect.center().y() - currentVisible.center().y(); if (currentVisible.y() > viewRect.y()) // move up verticalMove = viewRect.y() - currentVisible.y() - jumpHeight; else if (currentVisible.bottom() < viewRect.bottom()) // move down verticalMove = viewRect.bottom() - qMax(0, currentVisible.bottom() - jumpHeight); pan(QPoint(horizontalMove, verticalMove)); } void KoCanvasControllerWidget::recenterPreferred() { const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; QPointF center = preferredCenter(); // convert into a viewport based point center.rx() += d->canvas->canvasWidget()->x() + frameWidth(); center.ry() += d->canvas->canvasWidget()->y() + frameWidth(); // scroll to a new center point QPointF topLeft = center - 0.5 * QPointF(viewport()->width(), viewport()->height()); setScrollBarValue(topLeft.toPoint()); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::zoomIn(const QPoint ¢er) { zoomBy(center, sqrt(2.0)); } void KoCanvasControllerWidget::zoomOut(const QPoint ¢er) { zoomBy(center, sqrt(0.5)); } void KoCanvasControllerWidget::zoomBy(const QPoint ¢er, qreal zoom) { setPreferredCenterFractionX(1.0 * center.x() / documentSize().width()); setPreferredCenterFractionY(1.0 * center.y() / documentSize().height()); const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; proxyObject->emitZoomRelative(zoom, preferredCenter()); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::zoomTo(const QRect &viewRect) { qreal scale; if (1.0 * viewport()->width() / viewRect.width() > 1.0 * viewport()->height() / viewRect.height()) scale = 1.0 * viewport()->height() / viewRect.height(); else scale = 1.0 * viewport()->width() / viewRect.width(); zoomBy(viewRect.center(), scale); } void KoCanvasControllerWidget::updateDocumentSize(const QSize &sz, bool recalculateCenter) { // Don't update if the document-size didn't changed to prevent infinite loops and unneeded updates. if (KoCanvasController::documentSize() == sz) return; if (!recalculateCenter) { // assume the distance from the top stays equal and recalculate the center. setPreferredCenterFractionX(documentSize().width() * preferredCenterFractionX() / sz.width()); setPreferredCenterFractionY(documentSize().height() * preferredCenterFractionY() / sz.height()); } const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; KoCanvasController::setDocumentSize(sz); d->viewportWidget->setDocumentSize(sz); d->resetScrollBars(); // Always emit the new offset. updateCanvasOffsetX(); updateCanvasOffsetY(); d->ignoreScrollSignals = oldIgnoreScrollSignals; } void KoCanvasControllerWidget::setZoomWithWheel(bool zoom) { d->zoomWithWheel = zoom; } void KoCanvasControllerWidget::setVastScrolling(qreal factor) { d->vastScrollingFactor = factor; } void KoCanvasControllerWidget::pan(const QPoint &distance) { QPoint sourcePoint = scrollBarValue(); setScrollBarValue(sourcePoint + distance); } void KoCanvasControllerWidget::setPreferredCenter(const QPointF &viewPoint) { setPreferredCenterFractionX(viewPoint.x() / documentSize().width()); setPreferredCenterFractionY(viewPoint.y() / documentSize().height()); recenterPreferred(); } QPointF KoCanvasControllerWidget::preferredCenter() const { QPointF center; center.setX(preferredCenterFractionX() * documentSize().width()); center.setY(preferredCenterFractionY() * documentSize().height()); return center; } void KoCanvasControllerWidget::paintEvent(QPaintEvent *event) { QPainter gc(viewport()); d->viewportWidget->handlePaintEvent(gc, event); } void KoCanvasControllerWidget::dragEnterEvent(QDragEnterEvent *event) { d->viewportWidget->handleDragEnterEvent(event); } void KoCanvasControllerWidget::dropEvent(QDropEvent *event) { d->viewportWidget->handleDropEvent(event); } void KoCanvasControllerWidget::dragMoveEvent(QDragMoveEvent *event) { d->viewportWidget->handleDragMoveEvent(event); } void KoCanvasControllerWidget::dragLeaveEvent(QDragLeaveEvent *event) { d->viewportWidget->handleDragLeaveEvent(event); } void KoCanvasControllerWidget::keyPressEvent(QKeyEvent *event) { KoToolManager::instance()->priv()->switchToolByShortcut(event); } void KoCanvasControllerWidget::wheelEvent(QWheelEvent *event) { if (d->zoomWithWheel != ((event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier)) { const qreal zoomCoeff = event->delta() > 0 ? sqrt(2.0) : sqrt(0.5); zoomRelativeToPoint(event->pos(), zoomCoeff); event->accept(); } else QAbstractScrollArea::wheelEvent(event); } void KoCanvasControllerWidget::zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff) { const QPoint offset = scrollBarValue(); const QPoint mousePos(widgetPoint + offset); const bool oldIgnoreScrollSignals = d->ignoreScrollSignals; d->ignoreScrollSignals = true; proxyObject->emitZoomRelative(zoomCoeff, mousePos); d->ignoreScrollSignals = oldIgnoreScrollSignals; } bool KoCanvasControllerWidget::focusNextPrevChild(bool) { // we always return false meaning the canvas takes keyboard focus, but never gives it away. return false; } void KoCanvasControllerWidget::setMargin(int margin) { KoCanvasController::setMargin(margin); Q_ASSERT(d->viewportWidget); d->viewportWidget->setMargin(margin); } QPoint KoCanvasControllerWidget::scrollBarValue() const { QScrollBar * hBar = horizontalScrollBar(); QScrollBar * vBar = verticalScrollBar(); return QPoint(hBar->value(), vBar->value()); } void KoCanvasControllerWidget::setScrollBarValue(const QPoint &value) { QScrollBar * hBar = horizontalScrollBar(); QScrollBar * vBar = verticalScrollBar(); hBar->setValue(value.x()); vBar->setValue(value.y()); } KoCanvasControllerWidget::Private *KoCanvasControllerWidget::priv() { return d; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoCanvasControllerWidget.cpp" diff --git a/libs/flake/KoToolProxy.cpp b/libs/flake/KoToolProxy.cpp index 43e13039fd..3e30f6ccb5 100644 --- a/libs/flake/KoToolProxy.cpp +++ b/libs/flake/KoToolProxy.cpp @@ -1,648 +1,648 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (c) 2006-2011 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoToolProxy.h" #include "KoToolProxy_p.h" #include #include #include #include #include #include #include #include #include #include #include "KoToolBase.h" #include "KoPointerEvent.h" #include "KoInputDevice.h" #include "KoToolManager_p.h" #include "KoToolSelection.h" #include "KoCanvasBase.h" #include "KoCanvasController.h" #include "KoShapeManager.h" #include "KoSelection.h" #include "KoShapeLayer.h" #include "KoShapePaste.h" #include "KoShapeRegistry.h" #include "KoShapeController.h" #include "KoOdf.h" #include "KoViewConverter.h" #include "KoShapeFactoryBase.h" KoToolProxyPrivate::KoToolProxyPrivate(KoToolProxy *p) : activeTool(0), tabletPressed(false), hasSelection(false), controller(0), parent(p) { scrollTimer.setInterval(100); mouseLeaveWorkaround = false; multiClickCount = 0; } void KoToolProxyPrivate::timeout() // Auto scroll the canvas { Q_ASSERT(controller); QPoint offset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY()); QPoint origin = controller->canvas()->documentOrigin(); QPoint viewPoint = widgetScrollPoint - origin - offset; QRectF mouseArea(viewPoint, QSizeF(10, 10)); mouseArea.setTopLeft(mouseArea.center()); controller->ensureVisible(mouseArea, true); QPoint newOffset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY()); QPoint moved = offset - newOffset; if (moved.isNull()) return; widgetScrollPoint += moved; QPointF documentPoint = parent->widgetToDocument(widgetScrollPoint); QMouseEvent event(QEvent::MouseMove, widgetScrollPoint, Qt::LeftButton, Qt::LeftButton, 0); KoPointerEvent ev(&event, documentPoint); activeTool->mouseMoveEvent(&ev); } void KoToolProxyPrivate::checkAutoScroll(const KoPointerEvent &event) { if (controller == 0) return; if (!activeTool) return; if (!activeTool->wantsAutoScroll()) return; if (!event.isAccepted()) return; if (event.buttons() != Qt::LeftButton) return; widgetScrollPoint = event.pos(); if (! scrollTimer.isActive()) scrollTimer.start(); } void KoToolProxyPrivate::selectionChanged(bool newSelection) { if (hasSelection == newSelection) return; hasSelection = newSelection; emit parent->selectionChanged(hasSelection); } bool KoToolProxyPrivate::isActiveLayerEditable() { if (!activeTool) return false; KoShapeManager * shapeManager = activeTool->canvas()->shapeManager(); KoShapeLayer * activeLayer = shapeManager->selection()->activeLayer(); if (activeLayer && !activeLayer->isEditable()) return false; return true; } KoToolProxy::KoToolProxy(KoCanvasBase *canvas, QObject *parent) : QObject(parent), d(new KoToolProxyPrivate(this)) { KoToolManager::instance()->priv()->registerToolProxy(this, canvas); connect(&d->scrollTimer, SIGNAL(timeout()), this, SLOT(timeout())); } KoToolProxy::~KoToolProxy() { delete d; } void KoToolProxy::paint(QPainter &painter, const KoViewConverter &converter) { if (d->activeTool) d->activeTool->paint(painter, converter); } void KoToolProxy::repaintDecorations() { if (d->activeTool) d->activeTool->repaintDecorations(); } QPointF KoToolProxy::widgetToDocument(const QPointF &widgetPoint) const { QPoint offset = QPoint(d->controller->canvasOffsetX(), d->controller->canvasOffsetY()); QPoint origin = d->controller->canvas()->documentOrigin(); QPointF viewPoint = widgetPoint.toPoint() - QPointF(origin - offset); return d->controller->canvas()->viewConverter()->viewToDocument(viewPoint); } KoCanvasBase* KoToolProxy::canvas() const { return d->controller->canvas(); } void KoToolProxy::touchEvent(QTouchEvent *event) { QPointF point; QList touchPoints; bool isPrimary = true; Q_FOREACH (QTouchEvent::TouchPoint p, event->touchPoints()) { QPointF docPoint = widgetToDocument(p.screenPos()); if (isPrimary) { point = docPoint; isPrimary = false; } KoTouchPoint touchPoint; touchPoint.touchPoint = p; touchPoint.point = point; touchPoint.lastPoint = widgetToDocument(p.lastNormalizedPos()); touchPoints << touchPoint; } KoPointerEvent ev(event, point, touchPoints); KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); switch (event->type()) { case QEvent::TouchBegin: ev.setTabletButton(Qt::LeftButton); if (d->activeTool) { if( d->activeTool->wantsTouch() ) d->activeTool->touchEvent(event); else d->activeTool->mousePressEvent(&ev); } break; case QEvent::TouchUpdate: ev.setTabletButton(Qt::LeftButton); if (d->activeTool) { if( d->activeTool->wantsTouch() ) d->activeTool->touchEvent(event); else d->activeTool->mouseMoveEvent(&ev); } break; case QEvent::TouchEnd: ev.setTabletButton(Qt::LeftButton); if (d->activeTool) { if( d->activeTool->wantsTouch() ) d->activeTool->touchEvent(event); else d->activeTool->mouseReleaseEvent(&ev); } break; default: ; // ingore the rest } d->mouseLeaveWorkaround = true; } void KoToolProxy::tabletEvent(QTabletEvent *event, const QPointF &point) { // We get these events exclusively from KisToolProxy - accept them event->accept(); KoInputDevice id(event->device(), event->pointerType(), event->uniqueId()); KoToolManager::instance()->priv()->switchInputDevice(id); KoPointerEvent ev(event, point); switch (event->type()) { case QEvent::TabletPress: ev.setTabletButton(Qt::LeftButton); if (!d->tabletPressed && d->activeTool) d->activeTool->mousePressEvent(&ev); d->tabletPressed = true; break; case QEvent::TabletRelease: ev.setTabletButton(Qt::LeftButton); d->tabletPressed = false; d->scrollTimer.stop(); if (d->activeTool) d->activeTool->mouseReleaseEvent(&ev); break; case QEvent::TabletMove: if (d->tabletPressed) ev.setTabletButton(Qt::LeftButton); if (d->activeTool) d->activeTool->mouseMoveEvent(&ev); d->checkAutoScroll(ev); default: ; // ignore the rest. } d->mouseLeaveWorkaround = true; } void KoToolProxy::mousePressEvent(KoPointerEvent *ev) { d->mouseLeaveWorkaround = false; KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); d->mouseDownPoint = ev->pos(); if (d->tabletPressed) // refuse to send a press unless there was a release first. return; QPointF globalPoint = ev->globalPos(); if (d->multiClickGlobalPoint != globalPoint) { if (qAbs(globalPoint.x() - d->multiClickGlobalPoint.x()) > 5|| qAbs(globalPoint.y() - d->multiClickGlobalPoint.y()) > 5) { d->multiClickCount = 0; } d->multiClickGlobalPoint = globalPoint; } if (d->multiClickCount && d->multiClickTimeStamp.elapsed() < QApplication::doubleClickInterval()) { // One more multiclick; d->multiClickCount++; } else { d->multiClickTimeStamp.start(); d->multiClickCount = 1; } if (d->activeTool) { switch (d->multiClickCount) { case 0: case 1: d->activeTool->mousePressEvent(ev); break; case 2: d->activeTool->mouseDoubleClickEvent(ev); break; case 3: default: d->activeTool->mouseTripleClickEvent(ev); break; } } else { d->multiClickCount = 0; ev->ignore(); } } void KoToolProxy::mousePressEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mousePressEvent(&ev); } void KoToolProxy::mouseDoubleClickEvent(QMouseEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); mouseDoubleClickEvent(&ev); } void KoToolProxy::mouseDoubleClickEvent(KoPointerEvent *event) { // let us handle it as any other mousepress (where we then detect multi clicks mousePressEvent(event); if (!event->isAccepted() && d->activeTool) d->activeTool->canvas()->shapeManager()->suggestChangeTool(event); } void KoToolProxy::mouseMoveEvent(QMouseEvent *event, const QPointF &point) { if (d->mouseLeaveWorkaround) { d->mouseLeaveWorkaround = false; return; } KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); if (d->activeTool == 0) { event->ignore(); return; } KoPointerEvent ev(event, point); d->activeTool->mouseMoveEvent(&ev); d->checkAutoScroll(ev); } void KoToolProxy::mouseMoveEvent(KoPointerEvent *event) { if (d->mouseLeaveWorkaround) { d->mouseLeaveWorkaround = false; return; } KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); if (d->activeTool == 0) { event->ignore(); return; } d->activeTool->mouseMoveEvent(event); d->checkAutoScroll(*event); } void KoToolProxy::mouseReleaseEvent(QMouseEvent *event, const QPointF &point) { d->mouseLeaveWorkaround = false; KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); d->scrollTimer.stop(); KoPointerEvent ev(event, point); if (d->activeTool) { d->activeTool->mouseReleaseEvent(&ev); if (! event->isAccepted() && event->button() == Qt::LeftButton && event->modifiers() == 0 && qAbs(d->mouseDownPoint.x() - event->x()) < 5 && qAbs(d->mouseDownPoint.y() - event->y()) < 5) { // we potentially will change the selection Q_ASSERT(d->activeTool->canvas()); KoShapeManager *manager = d->activeTool->canvas()->shapeManager(); Q_ASSERT(manager); // only change the selection if that will not lead to losing a complex selection - if (manager->selection()->count() <= 1) { + if (manager->selection() && manager->selection()->count() <= 1) { KoShape *shape = manager->shapeAt(point); if (shape && !manager->selection()->isSelected(shape)) { // make the clicked shape the active one manager->selection()->deselectAll(); manager->selection()->select(shape); QList shapes; shapes << shape; QString tool = KoToolManager::instance()->preferredToolForSelection(shapes); KoToolManager::instance()->switchToolRequested(tool); } } } } else { event->ignore(); } } void KoToolProxy::mouseReleaseEvent(KoPointerEvent* event) { d->mouseLeaveWorkaround = false; KoInputDevice id; KoToolManager::instance()->priv()->switchInputDevice(id); d->scrollTimer.stop(); if (d->activeTool) { d->activeTool->mouseReleaseEvent(event); if (!event->isAccepted() && event->button() == Qt::LeftButton && event->modifiers() == 0 && qAbs(d->mouseDownPoint.x() - event->x()) < 5 && qAbs(d->mouseDownPoint.y() - event->y()) < 5) { // we potentially will change the selection Q_ASSERT(d->activeTool->canvas()); KoShapeManager *manager = d->activeTool->canvas()->shapeManager(); Q_ASSERT(manager); // only change the selection if that will not lead to losing a complex selection - if (manager->selection()->count() <= 1) { + if (manager->selection() && manager->selection()->count() <= 1) { KoShape *shape = manager->shapeAt(event->point); if (shape && !manager->selection()->isSelected(shape)) { // make the clicked shape the active one manager->selection()->deselectAll(); manager->selection()->select(shape); QList shapes; shapes << shape; QString tool = KoToolManager::instance()->preferredToolForSelection(shapes); KoToolManager::instance()->switchToolRequested(tool); } } } } else { event->ignore(); } } void KoToolProxy::keyPressEvent(QKeyEvent *event) { if (d->activeTool) d->activeTool->keyPressEvent(event); else event->ignore(); } void KoToolProxy::keyReleaseEvent(QKeyEvent *event) { if (d->activeTool) d->activeTool->keyReleaseEvent(event); else event->ignore(); } void KoToolProxy::wheelEvent(QWheelEvent *event, const QPointF &point) { KoPointerEvent ev(event, point); if (d->activeTool) d->activeTool->wheelEvent(&ev); else event->ignore(); } void KoToolProxy::wheelEvent(KoPointerEvent *event) { if (d->activeTool) d->activeTool->wheelEvent(event); else event->ignore(); } QVariant KoToolProxy::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const { if (d->activeTool) return d->activeTool->inputMethodQuery(query, converter); return QVariant(); } void KoToolProxy::inputMethodEvent(QInputMethodEvent *event) { if (d->activeTool) d->activeTool->inputMethodEvent(event); } void KoToolProxy::setActiveTool(KoToolBase *tool) { if (d->activeTool) disconnect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool))); d->activeTool = tool; if (tool) { connect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool))); d->selectionChanged(hasSelection()); emit toolChanged(tool->toolId()); } } void KoToolProxyPrivate::setCanvasController(KoCanvasController *c) { controller = c; } QHash KoToolProxy::actions() const { return d->activeTool ? d->activeTool->actions() : QHash(); } bool KoToolProxy::hasSelection() const { return d->activeTool ? d->activeTool->hasSelection() : false; } void KoToolProxy::cut() { if (d->activeTool && d->isActiveLayerEditable()) d->activeTool->cut(); } void KoToolProxy::copy() const { if (d->activeTool) d->activeTool->copy(); } bool KoToolProxy::paste() { bool success = false; KoCanvasBase *canvas = d->controller->canvas(); if (d->activeTool && d->isActiveLayerEditable()) success = d->activeTool->paste(); if (!success) { const QMimeData *data = QApplication::clipboard()->mimeData(); if (data->hasFormat(KoOdf::mimeType(KoOdf::Text))) { KoShapeManager *shapeManager = canvas->shapeManager(); KoShapePaste paste(canvas, shapeManager->selection()->activeLayer()); success = paste.paste(KoOdf::Text, data); if (success) { shapeManager->selection()->deselectAll(); Q_FOREACH (KoShape *shape, paste.pastedShapes()) { shapeManager->selection()->select(shape); } } } } if (!success) { const QMimeData *data = QApplication::clipboard()->mimeData(); QList imageList; QImage image = QApplication::clipboard()->image(); if (!image.isNull()) { imageList << image; } // QT5TODO: figure out how to download data synchronously, which is deprecated in frameworks. else if (data->hasUrls()) { QList urls = QApplication::clipboard()->mimeData()->urls(); foreach (const QUrl &url, urls) { QImage image; image.load(url.toLocalFile()); if (!image.isNull()) { imageList << image; } } } KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("PictureShape"); QWidget *canvasWidget = canvas->canvasWidget(); const KoViewConverter *converter = canvas->viewConverter(); if (imageList.length() > 0 && factory && canvasWidget) { KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Paste Image")); Q_FOREACH (const QImage &image, imageList) { if (!image.isNull()) { QPointF p = converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + canvas->canvasController()->documentOffset()- canvasWidget->pos()); KoProperties params; params.setProperty("qimage", image); KoShape *shape = factory->createShape(¶ms, canvas->shapeController()->resourceManager()); shape->setPosition(p); // add shape to the document canvas->shapeController()->addShapeDirect(shape, cmd); success = true; } } canvas->addCommand(cmd); } } return success; } void KoToolProxy::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { if (d->activeTool) d->activeTool->dragMoveEvent(event, point); } void KoToolProxy::dragLeaveEvent(QDragLeaveEvent *event) { if (d->activeTool) d->activeTool->dragLeaveEvent(event); } void KoToolProxy::dropEvent(QDropEvent *event, const QPointF &point) { if (d->activeTool) d->activeTool->dropEvent(event, point); } QStringList KoToolProxy::supportedPasteMimeTypes() const { if (d->activeTool) return d->activeTool->supportedPasteMimeTypes(); return QStringList(); } QList KoToolProxy::popupActionList() const { if (d->activeTool) return d->activeTool->popupActionList(); return QList(); } void KoToolProxy::deleteSelection() { if (d->activeTool) return d->activeTool->deleteSelection(); } void KoToolProxy::processEvent(QEvent *e) const { if(e->type()==QEvent::ShortcutOverride && d->activeTool && d->activeTool->isInTextMode() && (static_cast(e)->modifiers()==Qt::NoModifier || static_cast(e)->modifiers()==Qt::ShiftModifier)) { e->accept(); } } KoToolProxyPrivate *KoToolProxy::priv() { return d; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoToolProxy.cpp" diff --git a/libs/flake/tests/TestShapeStrokeCommand.cpp b/libs/flake/tests/TestShapeStrokeCommand.cpp index f08d48ac3b..2a1ea00433 100644 --- a/libs/flake/tests/TestShapeStrokeCommand.cpp +++ b/libs/flake/tests/TestShapeStrokeCommand.cpp @@ -1,72 +1,72 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TestShapeStrokeCommand.h" #include #include "KoShapeStrokeModel.h" #include "KoShapeStroke.h" #include "KoShapeStrokeCommand.h" #include #include void TestShapeStrokeCommand::refCounting() { MockShape * shape1 = new MockShape(); KoShapeStrokeModel *whiteStroke = new KoShapeStroke(1.0, QColor(Qt::white)); KoShapeStrokeModel *blackStroke = new KoShapeStroke(1.0, QColor(Qt::black)); KoShapeStrokeModel *redStroke = new KoShapeStroke(1.0, QColor(Qt::red)); shape1->setStroke(whiteStroke); QVERIFY(shape1->stroke() == whiteStroke); QCOMPARE(whiteStroke->useCount(), 1); // old stroke is white, new stroke is black KUndo2Command *cmd1 = new KoShapeStrokeCommand(shape1, blackStroke); cmd1->redo(); QVERIFY(shape1->stroke() == blackStroke); // change stroke back to white stroke cmd1->undo(); QVERIFY(shape1->stroke() == whiteStroke); // old stroke is white, new stroke is red KUndo2Command *cmd2 = new KoShapeStrokeCommand(shape1, redStroke); cmd2->redo(); QVERIFY(shape1->stroke() == redStroke); // this command has the white stroke as the old stroke delete cmd1; // set stroke back to white stroke cmd2->undo(); QVERIFY(shape1->stroke() == whiteStroke); // if white is deleted when deleting cmd1 this will crash KoInsets insets; whiteStroke->strokeInsets(shape1, insets); delete cmd2; - delete shape1; - delete whiteStroke; + delete shape1; // This deletes whiteStroke + delete blackStroke; delete redStroke; } QTEST_MAIN(TestShapeStrokeCommand) diff --git a/libs/image/kis_base_node.cpp b/libs/image/kis_base_node.cpp index e3d7fa9bf0..ab8c68c011 100644 --- a/libs/image/kis_base_node.cpp +++ b/libs/image/kis_base_node.cpp @@ -1,428 +1,434 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_base_node.h" #include #include #include #include #include #include "kis_paint_device.h" #include "kis_layer_properties_icons.h" #include "kis_scalar_keyframe_channel.h" struct Q_DECL_HIDDEN KisBaseNode::Private { QString compositeOp; KoProperties properties; KisBaseNode::Property hack_visible; //HACK QUuid id; QMap keyframeChannels; QScopedPointer opacityChannel; bool systemLocked; bool collapsed; bool supportsLodMoves; bool animated; bool useInTimeline; Private() : id(QUuid::createUuid()) , systemLocked(false) , collapsed(false) , supportsLodMoves(false) , animated(false) , useInTimeline(false) { } Private(const Private &rhs) : compositeOp(rhs.compositeOp), id(QUuid::createUuid()), systemLocked(false), collapsed(rhs.collapsed), supportsLodMoves(rhs.supportsLodMoves), animated(rhs.animated), useInTimeline(rhs.useInTimeline) { QMapIterator iter = rhs.properties.propertyIterator(); while (iter.hasNext()) { iter.next(); properties.setProperty(iter.key(), iter.value()); } } }; KisBaseNode::KisBaseNode() : m_d(new Private()) { /** * Be cautious! These two calls are vital to warm-up KoProperties. * We use it and its QMap in a threaded environment. This is not * officially suported by Qt, but our environment guarantees, that * there will be the only writer and several readers. Whilst the * value of the QMap is boolean and there are no implicit-sharing * calls provocated, it is safe to work with it in such an * environment. */ setVisible(true, true); setUserLocked(false); setCollapsed(false); setSupportsLodMoves(true); m_d->compositeOp = COMPOSITE_OVER; } KisBaseNode::KisBaseNode(const KisBaseNode & rhs) : QObject() , KisShared() , m_d(new Private(*rhs.m_d)) { } KisBaseNode::~KisBaseNode() { delete m_d; } quint8 KisBaseNode::opacity() const { if (m_d->opacityChannel) { qreal value = m_d->opacityChannel->currentValue(); if (!qIsNaN(value)) { return value; } } return nodeProperties().intProperty("opacity", OPACITY_OPAQUE_U8); } void KisBaseNode::setOpacity(quint8 val) { if (m_d->opacityChannel) { KisKeyframeSP activeKeyframe = m_d->opacityChannel->currentlyActiveKeyframe(); if (activeKeyframe) { m_d->opacityChannel->setScalarValue(activeKeyframe, val); } } if (opacity() == val) return; nodeProperties().setProperty("opacity", val); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } quint8 KisBaseNode::percentOpacity() const { return int(float(opacity() * 100) / 255 + 0.5); } void KisBaseNode::setPercentOpacity(quint8 val) { setOpacity(int(float(val * 255) / 100 + 0.5)); } const QString& KisBaseNode::compositeOpId() const { return m_d->compositeOp; } void KisBaseNode::setCompositeOpId(const QString& compositeOp) { if (m_d->compositeOp == compositeOp) return; m_d->compositeOp = compositeOp; baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } KisBaseNode::PropertyList KisBaseNode::sectionModelProperties() const { KisBaseNode::PropertyList l; l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::visible, visible(), m_d->hack_visible.isInStasis, m_d->hack_visible.stateInStasis); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::locked, userLocked()); return l; } void KisBaseNode::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { setVisible(properties.at(0).state.toBool()); m_d->hack_visible = properties.at(0); setUserLocked(properties.at(1).state.toBool()); } KoProperties & KisBaseNode::nodeProperties() const { return m_d->properties; } void KisBaseNode::mergeNodeProperties(const KoProperties & properties) { QMapIterator iter = properties.propertyIterator(); while (iter.hasNext()) { iter.next(); m_d->properties.setProperty(iter.key(), iter.value()); } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } bool KisBaseNode::check(const KoProperties & properties) const { QMapIterator iter = properties.propertyIterator(); while (iter.hasNext()) { iter.next(); if (m_d->properties.contains(iter.key())) { if (m_d->properties.value(iter.key()) != iter.value()) return false; } } return true; } QImage KisBaseNode::createThumbnail(qint32 w, qint32 h) { try { QImage image(w, h, QImage::Format_ARGB32); image.fill(0); return image; } catch (std::bad_alloc) { return QImage(); } } +QImage KisBaseNode::createThumbnailForFrame(qint32 w, qint32 h, int time) +{ + Q_UNUSED(time) + return createThumbnail(w, h); +} + bool KisBaseNode::visible(bool recursive) const { bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true); KisBaseNodeSP parentNode = parentCallback(); return recursive && isVisible && parentNode ? parentNode->visible() : isVisible; } void KisBaseNode::setVisible(bool visible, bool loading) { const bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true); if (!loading && isVisible == visible) return; m_d->properties.setProperty(KisLayerPropertiesIcons::visible.id(), visible); notifyParentVisibilityChanged(visible); if (!loading) { emit visibilityChanged(visible); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } bool KisBaseNode::userLocked() const { return m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), false); } void KisBaseNode::setUserLocked(bool locked) { const bool isLocked = m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), true); if (isLocked == locked) return; m_d->properties.setProperty(KisLayerPropertiesIcons::locked.id(), locked); emit userLockingChanged(locked); baseNodeChangedCallback(); } bool KisBaseNode::systemLocked() const { return m_d->systemLocked; } void KisBaseNode::setSystemLocked(bool locked, bool update) { m_d->systemLocked = locked; if (update) { emit systemLockingChanged(locked); baseNodeChangedCallback(); } } bool KisBaseNode::isEditable(bool checkVisibility) const { bool editable = true; if (checkVisibility) { editable = (visible(false) && !userLocked() && !systemLocked()); } else { editable = (!userLocked() && !systemLocked()); } if (editable) { KisBaseNodeSP parentNode = parentCallback(); if (parentNode && parentNode != this) { editable = parentNode->isEditable(checkVisibility); } } return editable; } bool KisBaseNode::hasEditablePaintDevice() const { return paintDevice() && isEditable(); } void KisBaseNode::setCollapsed(bool collapsed) { m_d->collapsed = collapsed; } bool KisBaseNode::collapsed() const { return m_d->collapsed; } void KisBaseNode::setColorLabelIndex(int index) { const int currentLabel = colorLabelIndex(); if (currentLabel == index) return; m_d->properties.setProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), index); baseNodeChangedCallback(); } int KisBaseNode::colorLabelIndex() const { return m_d->properties.intProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), 0); } QUuid KisBaseNode::uuid() const { return m_d->id; } void KisBaseNode::setUuid(const QUuid& id) { m_d->id = id; baseNodeChangedCallback(); } bool KisBaseNode::supportsLodMoves() const { return m_d->supportsLodMoves; } void KisBaseNode::setImage(KisImageWSP image) { Q_UNUSED(image); } void KisBaseNode::setSupportsLodMoves(bool value) { m_d->supportsLodMoves = value; } QList KisBaseNode::keyframeChannels() const { return m_d->keyframeChannels.values(); } KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id) const { QMap::const_iterator i = m_d->keyframeChannels.constFind(id); if (i == m_d->keyframeChannels.constEnd()) { return 0; } return i.value(); } KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id, bool create) { KisKeyframeChannel *channel = getKeyframeChannel(id); if (!channel && create) { channel = requestKeyframeChannel(id); if (channel) { addKeyframeChannel(channel); } } return channel; } bool KisBaseNode::isAnimated() const { return m_d->animated; } void KisBaseNode::enableAnimation() { m_d->animated = true; baseNodeChangedCallback(); } bool KisBaseNode::useInTimeline() const { return m_d->useInTimeline; } void KisBaseNode::setUseInTimeline(bool value) { if (value == m_d->useInTimeline) return; m_d->useInTimeline = value; baseNodeChangedCallback(); } void KisBaseNode::addKeyframeChannel(KisKeyframeChannel *channel) { m_d->keyframeChannels.insert(channel->id(), channel); emit keyframeChannelAdded(channel); } KisKeyframeChannel *KisBaseNode::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Opacity.id()) { Q_ASSERT(m_d->opacityChannel.isNull()); KisPaintDeviceSP device = original(); if (device) { KisScalarKeyframeChannel * channel = new KisScalarKeyframeChannel( KisKeyframeChannel::Opacity, 0, 255, device->defaultBounds(), KisKeyframe::Linear ); m_d->opacityChannel.reset(channel); return channel; } } return 0; } diff --git a/libs/image/kis_base_node.h b/libs/image/kis_base_node.h index 9e430ae2bc..5f0fe66625 100644 --- a/libs/image/kis_base_node.h +++ b/libs/image/kis_base_node.h @@ -1,579 +1,588 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_BASE_NODE_H #define _KIS_BASE_NODE_H #include #include #include #include #include #include "kis_shared.h" #include "kis_paint_device.h" #include "kis_processing_visitor.h" // included, not forward declared for msvc class KoProperties; class KoColorSpace; class KoCompositeOp; class KisNodeVisitor; class KisUndoAdapter; class KisKeyframeChannel; #include "kritaimage_export.h" /** * A KisBaseNode is the base class for all components of an image: * nodes, layers masks, selections. A node has a number of properties, * can be represented as a thumbnail and knows what to do when it gets * a certain paint device to process. A KisBaseNode does not know * anything about its peers. You should not directly inherit from a * KisBaseNode; inherit from KisNode instead. */ class KRITAIMAGE_EXPORT KisBaseNode : public QObject, public KisShared { Q_OBJECT public: /** * Describes a property of a document section. * * FIXME: using a QList instead of QMap and not having an untranslated identifier, * either enum or string, forces applications to rely on the order of properties * or to compare the translated strings. This makes it hard to robustly extend the * properties of document section items. */ struct Property { QString id; /** i18n-ed name, suitable for displaying */ QString name; /** Whether the property is a boolean (e.g. locked, visible) which can be toggled directly from the widget itself. */ bool isMutable; /** Provide these if the property isMutable. */ QIcon onIcon; QIcon offIcon; /** If the property isMutable, provide a boolean. Otherwise, a string suitable for displaying. */ QVariant state; /** If the property is mutable, specifies whether it can be put into stasis. When a property is in stasis, a new state is created, and the old one is stored in stateInStasis. When stasis ends, the old value is restored and the new one discarded */ bool canHaveStasis; /** If the property isMutable and canHaveStasis, indicate whether it is in stasis or not */ bool isInStasis; /** If the property isMutable and canHaveStasis, provide this value to store the property's state while in stasis */ bool stateInStasis; bool operator==(const Property &rhs) const { return rhs.name == name; } Property(): isMutable( false ) { } /// Constructor for a mutable property. Property( const KoID &n, const QIcon &on, const QIcon &off, bool isOn ) : id(n.id()), name( n.name() ), isMutable( true ), onIcon( on ), offIcon( off ), state( isOn ), canHaveStasis( false ) { } /** Constructor for a mutable property accepting stasis */ Property( const KoID &n, const QIcon &on, const QIcon &off, bool isOn, bool _isInStasis, bool _stateInStasis ) : id(n.id()), name(n.name()), isMutable( true ), onIcon( on ), offIcon( off ), state( isOn ), canHaveStasis( true ), isInStasis( _isInStasis ), stateInStasis( _stateInStasis ) { } /// Constructor for a nonmutable property. Property( const KoID &n, const QString &s ) : id(n.id()), name(n.name()), isMutable( false ), state( s ) { } }; /** Return this type for PropertiesRole. */ typedef QList PropertyList; public: /** * Create a new, empty base node. The node is unnamed, unlocked * visible and unlinked. */ KisBaseNode(); /** * Create a copy of this node. */ KisBaseNode(const KisBaseNode & rhs); /** * Delete this node */ virtual ~KisBaseNode(); /** * Return the paintdevice you can use to change pixels on. For a * paint layer these will be paint pixels, for an adjustment layer or a mask * the selection paint device. * * @return the paint device to paint on. Can be 0 if the actual * node type does not support painting. */ virtual KisPaintDeviceSP paintDevice() const = 0; /** * @return the rendered representation of a node * before the effect masks have had their go at it. Can be 0. */ virtual KisPaintDeviceSP original() const = 0; /** * @return the fully rendered representation of this layer: its * rendered original and its effect masks. Can be 0. */ virtual KisPaintDeviceSP projection() const = 0; virtual const KoColorSpace *colorSpace() const = 0; /** * Return the opacity of this layer, scaled to a range between 0 * and 255. * XXX: Allow true float opacity */ quint8 opacity() const; //0-255 /** * Set the opacity for this layer. The range is between 0 and 255. * The layer will be marked dirty. * * XXX: Allow true float opacity */ void setOpacity(quint8 val); //0-255 /** * return the 8-bit opacity of this layer scaled to the range * 0-100 * * XXX: Allow true float opacity */ quint8 percentOpacity() const; //0-100 /** * Set the opacity of this layer with a number between 0 and 100; * the number will be scaled to between 0 and 255. * XXX: Allow true float opacity */ void setPercentOpacity(quint8 val); //0-100 /** * Return the composite op associated with this layer. */ virtual const KoCompositeOp *compositeOp() const = 0; const QString& compositeOpId() const; /** * Set a new composite op for this layer. The layer will be marked * dirty. */ void setCompositeOpId(const QString& compositeOpId); /** * @return unique id, which is now used by clone layers. */ QUuid uuid() const; /** * Set the uuid of node. This should only be used when loading * existing node and in constructor. */ void setUuid(const QUuid& id); /** * return the name of this node. This is the same as the * QObject::objectName. */ QString name() const { return objectName(); } /** * set the QObject::objectName. This is also the user-visible name * of the layer. The reason for this is that we want to see the * layer name also when debugging. */ void setName(const QString& name) { setObjectName(name); baseNodeChangedCallback(); } /** * @return the icon used to represent the node type, for instance * in the layerbox and in the menu. */ virtual QIcon icon() const { return QIcon(); } /** * Return a the properties of this base node (locked, visible etc, * with the right icons for their representation and their state. * * Subclasses can extend this list with new properties, like * opacity for layers or visualized for masks. * * The order of properties is, unfortunately, for now, important, * so take care which properties superclasses of your class * define. * * KisBaseNode defines visible = 0, locked = 1 * KisLayer defines opacity = 2, compositeOp = 3 * KisMask defines active = 2 (KisMask does not inherit kislayer) */ virtual PropertyList sectionModelProperties() const; /** * Change the section model properties. */ virtual void setSectionModelProperties(const PropertyList &properties); /** * Return all the properties of this layer as a KoProperties-based * serializable key-value list. */ KoProperties & nodeProperties() const; /** * Merge the specified properties with the properties of this * layer. Whereever these properties overlap, the value of the * node properties is changed. No properties on the node are * deleted. If there are new properties in this list, they will be * added on the node. */ void mergeNodeProperties(const KoProperties & properties); /** * Compare the given properties list with the properties of this * node. * * @return false only if the same property exists in both lists * but with a different value. Properties that are not in both * lists are disregarded. */ bool check(const KoProperties & properties) const; /** * Accept the KisNodeVisitor (for the Visitor design pattern), * should call the correct function on the KisNodeVisitor for this * node type, so you need to override it for all leaf classes in * the node inheritance hierarchy. * * return false if the visitor could not successfully act on this * node instance. */ virtual bool accept(KisNodeVisitor &) { return false; } /** * Accept the KisNodeVisitor (for the Visitor design pattern), * should call the correct function on the KisProcessingVisitor * for this node type, so you need to override it for all leaf * classes in the node inheritance hierarchy. * * The processing visitor differs from node visitor in the way * that it accepts undo adapter, that allows the processing to * be multithreaded */ virtual void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { Q_UNUSED(visitor); Q_UNUSED(undoAdapter); } /** * @return a thumbnail in requested size. The thumbnail is a rgba * QImage and may have transparent parts. Returns a fully * transparent QImage of the requested size if the current node * type cannot generate a thumbnail. If the requested size is too * big, return a null QImage. */ virtual QImage createThumbnail(qint32 w, qint32 h); + /** + * @return a thumbnail in requested size for the defined timestamp. + * The thumbnail is a rgba Image and may have transparent parts. + * Returns a fully transparent QImage of the requested size if the + * current node type cannot generate a thumbnail. If the requested + * size is too big, return a null QImage. + */ + virtual QImage createThumbnailForFrame(qint32 w, qint32 h, int time); + /** * Ask this node to re-read the pertinent settings from the krita * configuration. */ virtual void updateSettings() { } /** * @return true if this node is visible (i.e, active (except for * selection masks where visible and active properties are * different)) in the graph */ virtual bool visible(bool recursive = false) const; /** * Set the visible status of this node. Visible nodes are active * in the graph (except for selections masks which can be active * while hidden), that is to say, they are taken into account * when merging. Invisible nodes play no role in the final image *, but will be modified when modifying all layers, for instance * when cropping. * * Toggling the visibility of a node will not automatically lead * to recomposition. * * @param visible the new visibility state * @param isLoading if true, the property is set during loading. */ virtual void setVisible(bool visibile, bool loading = false); /** * Return the locked status of this node. Locked nodes cannot be * edited. */ bool userLocked() const; /** * Set the locked status of this node. Locked nodes cannot be * edited. */ void setUserLocked(bool l); /** * Return the locked status of this node. System Locked nodes indicates * that an algorithm is processing them and that an other * algorithm need to wait before accessing it. */ bool systemLocked() const; /** * Set the locked status of this node. System Locked nodes indicates * that an algorithm is processing them and that an other * algorithm need to wait before accessing it. * * A KisNode will update the layer model when the lock is released. * * @param l lock state * @param update set false if the tools shouldn't be locked */ virtual void setSystemLocked(bool l, bool update = true); /** * @return true if the node can be edited: * * if checkVisibility is true, then the node is only editable if it is visible and not locked. * if checkVisibility is false, then the node is editable if it's not locked. */ bool isEditable(bool checkVisibility = true) const; /** * @return true if the node is editable and has a paintDevice() * which which can be used for accessing pixels. It is an * equivalent to (isEditable() && paintDevice()) */ bool hasEditablePaintDevice() const; /** * @return the x-offset of this layer in the image plane. */ virtual qint32 x() const { return 0; } /** * Set the x offset of this layer in the image place. * Re-implement this where it makes sense, by default it does * nothing. It should not move child nodes. */ virtual void setX(qint32) { } /** * @return the y-offset of this layer in the image plane. */ virtual qint32 y() const { return 0; } /** * Set the y offset of this layer in the image place. * Re-implement this where it makes sense, by default it does * nothing. It should not move child nodes. */ virtual void setY(qint32) { } /** * Returns an approximation of where the bounds on actual data are * in this node. */ virtual QRect extent() const { return QRect(); } /** * Returns the exact bounds of where the actual data resides in * this node. */ virtual QRect exactBounds() const { return QRect(); } /** * Sets the state of the node to the value of @param collapsed */ void setCollapsed(bool collapsed); /** * returns the collapsed state of this node */ bool collapsed() const; /** * Sets a color label index associated to the layer. The actual * color of the label and the number of available colors is * defined by Krita GUI configuration. */ void setColorLabelIndex(int index); /** * \see setColorLabelIndex */ int colorLabelIndex() const; /** * Returns true if the offset of the node can be changed in a LodN * stroke. Currently, all the nodes except shape layers support that. */ bool supportsLodMoves() const; /** * Return the keyframe channels associated with this node * @return list of keyframe channels */ QList keyframeChannels() const; /** * Get the keyframe channel with given id. * If the channel does not yet exist and the node supports the requested * channel, it will be created if create is true. * @param id internal name for channel * @param create attempt to create the channel if it does not exist yet * @return keyframe channel with the id, or null if not found */ KisKeyframeChannel *getKeyframeChannel(const QString &id, bool create); KisKeyframeChannel *getKeyframeChannel(const QString &id) const; bool useInTimeline() const; void setUseInTimeline(bool value); bool isAnimated() const; virtual void enableAnimation(); virtual void setImage(KisImageWSP image); protected: void setSupportsLodMoves(bool value); /** * FIXME: This method is a workaround for getting parent node * on a level of KisBaseNode. In fact, KisBaseNode should inherit * KisNode (in terms of current Krita) to be able to traverse * the node stack */ virtual KisBaseNodeSP parentCallback() const { return KisBaseNodeSP(); } virtual void notifyParentVisibilityChanged(bool value) { Q_UNUSED(value); } /** * This callback is called when some meta state of the base node * that can be interesting to the UI has changed. E.g. visibility, * lockness, opacity, compositeOp and etc. This signal is * forwarded by the KisNode and KisNodeGraphListener to the model * in KisLayerBox, so it can update its controls when information * changes. */ virtual void baseNodeChangedCallback() { } virtual void baseNodeInvalidateAllFramesCallback() { } /** * Add a keyframe channel for this node. The channel will be added * to the common hash table which will be available to the UI. * * WARNING: the \p channel object *NOT* become owned by the node! * The caller must ensure manually that the lifetime of * the object coincide with the lifetime of the node. */ virtual void addKeyframeChannel(KisKeyframeChannel* channel); /** * Attempt to create the requested channel. Used internally by getKeyframeChannel. * Subclasses should implement this method to catch any new channel types they support. * @param id channel to create * @return newly created channel or null */ virtual KisKeyframeChannel * requestKeyframeChannel(const QString &id); Q_SIGNALS: /** * This signal is emitted when the visibility of the layer is changed with \ref setVisible. */ void visibilityChanged(bool); /** * This signal is emitted when the node is locked or unlocked with \ref setUserLocked. */ void userLockingChanged(bool); /** * This signal is emitted when the node is locked or unlocked with \ref setSystemLocked. */ void systemLockingChanged(bool); void keyframeChannelAdded(KisKeyframeChannel *channel); private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE( KisBaseNode::PropertyList ) #endif diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index cc45b10e66..b3c914e7b1 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,1694 +1,1703 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image.h" #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorProfile.h" #include #include "KisProofingConfiguration.h" #include "recorder/kis_action_recorder.h" #include "kis_adjustment_layer.h" #include "kis_annotation.h" #include "kis_change_profile_visitor.h" #include "kis_colorspace_convert_visitor.h" #include "kis_count_visitor.h" #include "kis_filter_strategy.h" #include "kis_group_layer.h" #include "commands/kis_image_commands.h" #include "kis_layer.h" #include "kis_meta_data_merge_strategy_registry.h" #include "kis_name_server.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_transaction.h" #include "kis_meta_data_merge_strategy.h" #include "kis_memory_statistics_server.h" #include "kis_image_config.h" #include "kis_update_scheduler.h" #include "kis_image_signal_router.h" #include "kis_image_animation_interface.h" #include "kis_stroke_strategy.h" #include "kis_image_barrier_locker.h" #include "kis_undo_stores.h" #include "kis_legacy_undo_adapter.h" #include "kis_post_execution_undo_adapter.h" #include "kis_transform_worker.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_processing_visitor.h" #include "processing/kis_crop_selections_processing_visitor.h" #include "processing/kis_transform_processing_visitor.h" #include "commands_new/kis_image_resize_command.h" #include "commands_new/kis_image_set_resolution_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_composite_progress_proxy.h" #include "kis_layer_composition.h" #include "kis_wrapped_rect.h" #include "kis_crop_saved_extra_data.h" #include "kis_layer_utils.h" #include "kis_lod_transform.h" #include "kis_suspend_projection_updates_stroke_strategy.h" #include "kis_sync_lod_cache_stroke_strategy.h" #include "kis_projection_updates_filter.h" #include "kis_layer_projection_plane.h" #include "kis_update_time_monitor.h" #include "kis_image_barrier_locker.h" #include #include #include "kis_time_range.h" // #define SANITY_CHECKS #ifdef SANITY_CHECKS #define SANITY_CHECK_LOCKED(name) \ if (!locked()) warnKrita() << "Locking policy failed:" << name \ << "has been called without the image" \ "being locked"; #else #define SANITY_CHECK_LOCKED(name) #endif struct KisImageSPStaticRegistrar { KisImageSPStaticRegistrar() { qRegisterMetaType("KisImageSP"); } }; static KisImageSPStaticRegistrar __registrar; class KisImage::KisImagePrivate { public: KisImagePrivate(KisImage *_q, qint32 w, qint32 h, const KoColorSpace *c, KisUndoStore *undo, KisImageAnimationInterface *_animationInterface) : q(_q) , lockedForReadOnly(false) , width(w) , height(h) , colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8()) , nserver(1) , undoStore(undo ? undo : new KisDumbUndoStore()) , legacyUndoAdapter(undoStore.data(), _q) , postExecutionUndoAdapter(undoStore.data(), _q) , recorder(_q) , signalRouter(_q) , animationInterface(_animationInterface) , scheduler(_q) , axesCenter(QPointF(0.5, 0.5)) { { KisImageConfig cfg; if (cfg.enableProgressReporting()) { scheduler.setProgressProxy(&compositeProgressProxy); } // Each of these lambdas defines a new factory function. scheduler.setLod0ToNStrokeStrategyFactory( [=](bool forgettable) { return KisLodSyncPair( new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable), KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q))); }); scheduler.setSuspendUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true), KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q))); }); scheduler.setResumeUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false), KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q))); }); } connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged())); } ~KisImagePrivate() { /** * Stop animation interface. It may use the rootLayer. */ delete animationInterface; /** * First delete the nodes, while strokes * and undo are still alive */ rootLayer.clear(); } KisImage *q; quint32 lockCount = 0; bool lockedForReadOnly; qint32 width; qint32 height; double xres = 1.0; double yres = 1.0; const KoColorSpace * colorSpace; KisProofingConfigurationSP proofingConfig; KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; QScopedPointer undoStore; KisLegacyUndoAdapter legacyUndoAdapter; KisPostExecutionUndoAdapter postExecutionUndoAdapter; KisActionRecorder recorder; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; KisProjectionUpdatesFilterSP projectionUpdatesFilter; KisImageSignalRouter signalRouter; KisImageAnimationInterface *animationInterface; KisUpdateScheduler scheduler; QAtomicInt disableDirtyRequests; KisCompositeProgressProxy compositeProgressProxy; bool blockLevelOfDetail = false; QPointF axesCenter; bool tryCancelCurrentStrokeAsync(); void notifyProjectionUpdatedInPatches(const QRect &rc); }; KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name) : QObject(0) , KisShared() , m_d(new KisImagePrivate(this, width, height, colorSpace, undoStore, new KisImageAnimationInterface(this))) { setObjectName(name); setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); } KisImage::~KisImage() { qDebug() << "deleting kisimage" << objectName(); /** * Request the tools to end currently running strokes */ waitForDone(); delete m_d; disconnect(); // in case Qt gets confused } KisImage *KisImage::clone(bool exactCopy) { return new KisImage(*this, 0, exactCopy); } KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy) : KisNodeFacade(), KisNodeGraphListener(), KisShared(), m_d(new KisImagePrivate(this, rhs.width(), rhs.height(), rhs.colorSpace(), undoStore ? undoStore : new KisDumbUndoStore(), new KisImageAnimationInterface(*rhs.animationInterface(), this))) { setObjectName(rhs.objectName()); m_d->xres = rhs.m_d->xres; m_d->yres = rhs.m_d->yres; if (rhs.m_d->proofingConfig) { m_d->proofingConfig = toQShared(new KisProofingConfiguration(*rhs.m_d->proofingConfig)); } KisNodeSP newRoot = rhs.root()->clone(); newRoot->setGraphListener(this); newRoot->setImage(this); m_d->rootLayer = dynamic_cast(newRoot.data()); setRoot(newRoot); if (exactCopy) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(newRoot, [&linearizedNodes](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); node->setUuid(refNode->uuid()); }); } Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) { m_d->compositions << toQShared(new KisLayerComposition(*comp, this)); } rhs.m_d->nserver = KisNameServer(rhs.m_d->nserver); vKisAnnotationSP newAnnotations; Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) { newAnnotations << annotation->clone(); } m_d->annotations = newAnnotations; KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests); m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail; } void KisImage::aboutToAddANode(KisNode *parent, int index) { KisNodeGraphListener::aboutToAddANode(parent, index); SANITY_CHECK_LOCKED("aboutToAddANode"); } void KisImage::nodeHasBeenAdded(KisNode *parent, int index) { KisNodeGraphListener::nodeHasBeenAdded(parent, index); SANITY_CHECK_LOCKED("nodeHasBeenAdded"); m_d->signalRouter.emitNodeHasBeenAdded(parent, index); KisNodeSP newNode = parent->at(index); if (!dynamic_cast(newNode.data())) { stopIsolatedMode(); } } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data())) { stopIsolatedMode(); } KisNodeGraphListener::aboutToRemoveANode(parent, index); SANITY_CHECK_LOCKED("aboutToRemoveANode"); m_d->signalRouter.emitAboutToRemoveANode(parent, index); } void KisImage::nodeChanged(KisNode* node) { KisNodeGraphListener::nodeChanged(node); requestStrokeEnd(); m_d->signalRouter.emitNodeChanged(node); } void KisImage::invalidateAllFrames() { invalidateFrames(KisTimeRange::infinite(0), QRect()); } KisSelectionSP KisImage::globalSelection() const { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (selectionMask) { return selectionMask->selection(); } else { return 0; } } void KisImage::setGlobalSelection(KisSelectionSP globalSelection) { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (!globalSelection) { if (selectionMask) { removeNode(selectionMask); } } else { if (!selectionMask) { selectionMask = new KisSelectionMask(this); selectionMask->initSelection(m_d->rootLayer); addNode(selectionMask); // If we do not set the selection now, the setActive call coming next // can be very, very expensive, depending on the size of the image. selectionMask->setSelection(globalSelection); selectionMask->setActive(true); } else { selectionMask->setSelection(globalSelection); } Q_ASSERT(m_d->rootLayer->childCount() > 0); Q_ASSERT(m_d->rootLayer->selectionMask()); } m_d->deselectedGlobalSelection = 0; m_d->legacyUndoAdapter.emitSelectionChanged(); } void KisImage::deselectGlobalSelection() { KisSelectionSP savedSelection = globalSelection(); setGlobalSelection(0); m_d->deselectedGlobalSelection = savedSelection; } bool KisImage::canReselectGlobalSelection() { return m_d->deselectedGlobalSelection; } void KisImage::reselectGlobalSelection() { if(m_d->deselectedGlobalSelection) { setGlobalSelection(m_d->deselectedGlobalSelection); } } QString KisImage::nextLayerName(const QString &_baseName) const { QString baseName = _baseName; if (m_d->nserver.currentSeed() == 0) { m_d->nserver.number(); return i18n("background"); } if (baseName.isEmpty()) { baseName = i18n("Layer"); } return QString("%1 %2").arg(baseName).arg(m_d->nserver.number()); } void KisImage::rollBackLayerName() { m_d->nserver.rollback(); } KisCompositeProgressProxy* KisImage::compositeProgressProxy() { return &m_d->compositeProgressProxy; } bool KisImage::locked() const { return m_d->lockCount != 0; } void KisImage::barrierLock(bool readOnly) { if (!locked()) { requestStrokeEnd(); m_d->scheduler.barrierLock(); m_d->lockedForReadOnly = readOnly; } else { m_d->lockedForReadOnly &= readOnly; } m_d->lockCount++; } bool KisImage::tryBarrierLock(bool readOnly) { bool result = true; if (!locked()) { result = m_d->scheduler.tryBarrierLock(); m_d->lockedForReadOnly = readOnly; } if (result) { m_d->lockCount++; m_d->lockedForReadOnly &= readOnly; } return result; } bool KisImage::isIdle(bool allowLocked) { return (allowLocked || !locked()) && m_d->scheduler.isIdle(); } void KisImage::lock() { if (!locked()) { requestStrokeEnd(); m_d->scheduler.lock(); } m_d->lockCount++; m_d->lockedForReadOnly = false; } void KisImage::unlock() { Q_ASSERT(locked()); if (locked()) { m_d->lockCount--; if (m_d->lockCount == 0) { m_d->scheduler.unlock(!m_d->lockedForReadOnly); } } } void KisImage::blockUpdates() { m_d->scheduler.blockUpdates(); } void KisImage::unblockUpdates() { m_d->scheduler.unblockUpdates(); } void KisImage::setSize(const QSize& size) { m_d->width = size.width(); m_d->height = size.height(); } void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if (newRect == bounds() && !cropLayers) return; KUndo2MagicString actionName = cropLayers ? kundo2_i18n("Crop Image") : kundo2_i18n("Resize Image"); KisImageSignalVector emitSignals; emitSignals << ComplexSizeChangedSignal(newRect, newRect.size()); emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(cropLayers ? KisCropSavedExtraData::CROP_IMAGE : KisCropSavedExtraData::RESIZE_IMAGE, newRect); KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName, extraData); if (cropLayers || !newRect.topLeft().isNull()) { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.applyCommand(new KisImageResizeCommand(this, newRect.size())); applicator.end(); } void KisImage::resizeImage(const QRect& newRect) { resizeImageImpl(newRect, false); } void KisImage::cropImage(const QRect& newRect) { resizeImageImpl(newRect, true); } void KisImage::cropNode(KisNodeSP node, const QRect& newRect) { bool isLayer = qobject_cast(node.data()); KUndo2MagicString actionName = isLayer ? kundo2_i18n("Crop Layer") : kundo2_i18n("Crop Mask"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER, newRect, node); KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName, extraData); KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, true, false); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy) { bool resolutionChanged = xres != xRes() && yres != yRes(); bool sizeChanged = size != this->size(); if (!resolutionChanged && !sizeChanged) return; KisImageSignalVector emitSignals; if (resolutionChanged) emitSignals << ResolutionChangedSignal; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size); emitSignals << ModifiedSignal; KUndo2MagicString actionName = sizeChanged ? kundo2_i18n("Scale Image") : kundo2_i18n("Change Image Resolution"); KisProcessingApplicator::ProcessingFlags signalFlags = (resolutionChanged || sizeChanged) ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); qreal sx = qreal(size.width()) / this->size().width(); qreal sy = qreal(size.height()) / this->size().height(); QTransform shapesCorrection; if (resolutionChanged) { shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres); } KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(sx, sy, 0, 0, QPointF(), 0, 0, 0, filterStrategy, shapesCorrection); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resolutionChanged) { KUndo2Command *parent = new KisResetShapesCommand(m_d->rootLayer); new KisImageSetResolutionCommand(this, xres, yres, parent); applicator.applyCommand(parent); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, size)); } applicator.end(); } void KisImage::scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(scaleX, scaleY, 0, 0, QPointF(), 0, 0, 0, filterStrategy); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double radians) { QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, 0, 0, 0, 0, radians, 0, 0, 0, 0); QTransform transform = worker.transform(); if (resizeImage) { QRect newRect = transform.mapRect(bounds()); newSize = newRect.size(); offset = -newRect.topLeft(); } else { QPointF origin = QRectF(rootNode->exactBounds()).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && (newSize.width() != width() || newSize.height() != height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; // These flags determine whether updates are transferred to the UI during processing KisProcessingApplicator::ProcessingFlags signalFlags = sizeChanged ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, rootNode, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::rotateImage(double radians) { rotateImpl(kundo2_i18n("Rotate Image"), root(), true, radians); } void KisImage::rotateNode(KisNodeSP node, double radians) { - rotateImpl(kundo2_i18n("Rotate Layer"), node, false, radians); + if (node->inherits("KisMask")) { + rotateImpl(kundo2_i18n("Rotate Mask"), node, false, radians); + } else { + rotateImpl(kundo2_i18n("Rotate Layer"), node, false, radians); + } } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, const QPointF &origin) { //angleX, angleY are in degrees const qreal pi = 3.1415926535897932385; const qreal deg2rad = pi / 180.0; qreal tanX = tan(angleX * deg2rad); qreal tanY = tan(angleY * deg2rad); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, tanX, tanY, origin.x(), origin.y(), 0, 0, 0, 0, 0); QRect newRect = worker.transform().mapRect(bounds()); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } if (newSize == size()) return; KisImageSignalVector emitSignals; if (resizeImage) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; KisProcessingApplicator::ProcessingFlags signalFlags = KisProcessingApplicator::RECURSIVE; if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES; KisProcessingApplicator applicator(this, rootNode, signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY) { QPointF shearOrigin = QRectF(bounds()).center(); - shearImpl(kundo2_i18n("Shear layer"), node, false, - angleX, angleY, shearOrigin); + if (node->inherits("KisMask")) { + shearImpl(kundo2_i18n("Shear Mask"), node, false, + angleX, angleY, shearOrigin); + } else { + shearImpl(kundo2_i18n("Shear Layer"), node, false, + angleX, angleY, shearOrigin); + } } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, QPointF()); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (!dstColorSpace) return; const KoColorSpace *srcColorSpace = m_d->colorSpace; undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); m_d->rootLayer->accept(visitor); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } bool KisImage::assignImageProfile(const KoColorProfile *profile) { if (!profile) return false; const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); const KoColorSpace *srcCs = colorSpace(); if (!dstCs) return false; m_d->colorSpace = dstCs; KisChangeProfileVisitor visitor(srcCs, dstCs); bool retval = m_d->rootLayer->accept(visitor); m_d->signalRouter.emitNotification(ProfileChangedSignal); return retval; } void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace) { if (*m_d->colorSpace == *dstColorSpace) return; undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace) { m_d->colorSpace = colorSpace; m_d->rootLayer->resetCache(); m_d->signalRouter.emitNotification(ColorSpaceChangedSignal); } const KoColorSpace * KisImage::colorSpace() const { return m_d->colorSpace; } const KoColorProfile * KisImage::profile() const { return colorSpace()->profile(); } double KisImage::xRes() const { return m_d->xres; } double KisImage::yRes() const { return m_d->yres; } void KisImage::setResolution(double xres, double yres) { m_d->xres = xres; m_d->yres = yres; m_d->signalRouter.emitNotification(ResolutionChangedSignal); } QPointF KisImage::documentToPixel(const QPointF &documentCoord) const { return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes()); } QPoint KisImage::documentToIntPixel(const QPointF &documentCoord) const { QPointF pixelCoord = documentToPixel(documentCoord); return QPoint((int)pixelCoord.x(), (int)pixelCoord.y()); } QRectF KisImage::documentToPixel(const QRectF &documentRect) const { return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight())); } QRect KisImage::documentToIntPixel(const QRectF &documentRect) const { return documentToPixel(documentRect).toAlignedRect(); } QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const { return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes()); } QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const { return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes()); } QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const { return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight())); } qint32 KisImage::width() const { return m_d->width; } qint32 KisImage::height() const { return m_d->height; } KisGroupLayerSP KisImage::rootLayer() const { Q_ASSERT(m_d->rootLayer); return m_d->rootLayer; } KisPaintDeviceSP KisImage::projection() const { if (m_d->isolatedRootNode) { return m_d->isolatedRootNode->projection(); } Q_ASSERT(m_d->rootLayer); KisPaintDeviceSP projection = m_d->rootLayer->projection(); Q_ASSERT(projection); return projection; } qint32 KisImage::nlayers() const { QStringList list; list << "KisLayer"; KisCountVisitor visitor(list, KoProperties()); m_d->rootLayer->accept(visitor); return visitor.count(); } qint32 KisImage::nHiddenLayers() const { QStringList list; list << "KisLayer"; KoProperties properties; properties.setProperty("visible", false); KisCountVisitor visitor(list, properties); m_d->rootLayer->accept(visitor); return visitor.count(); } void KisImage::flatten() { KisLayerUtils::flattenImage(this); } void KisImage::mergeMultipleLayers(QList mergedNodes, KisNodeSP putAfter) { if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) { KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter); } } void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { KisLayerUtils::mergeDown(this, layer, strategy); } void KisImage::flattenLayer(KisLayerSP layer) { KisLayerUtils::flattenLayer(this, layer); } void KisImage::setModified() { m_d->signalRouter.emitNotification(ModifiedSignal); } QImage KisImage::convertToQImage(QRect imageRect, const KoColorProfile * profile) { qint32 x; qint32 y; qint32 w; qint32 h; imageRect.getRect(&x, &y, &w, &h); return convertToQImage(x, y, w, h, profile); } QImage KisImage::convertToQImage(qint32 x, qint32 y, qint32 w, qint32 h, const KoColorProfile * profile) { KisPaintDeviceSP dev = projection(); if (!dev) return QImage(); QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return image; } QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile) { if (scaledImageSize.isEmpty()) { return QImage(); } KisPaintDeviceSP dev = new KisPaintDevice(colorSpace()); KisPainter gc; gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds()); gc.end(); double scaleX = qreal(scaledImageSize.width()) / width(); double scaleY = qreal(scaledImageSize.height()) / height(); QPointer updater = new KoDummyUpdater(); KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); worker.run(); delete updater; return dev->convertToQImage(profile); } void KisImage::notifyLayersChanged() { m_d->signalRouter.emitNotification(LayersChangedSignal); } QRect KisImage::bounds() const { return QRect(0, 0, width(), height()); } QRect KisImage::effectiveLodBounds() const { QRect boundRect = bounds(); const int lod = currentLevelOfDetail(); if (lod > 0) { KisLodTransform t(lod); boundRect = t.map(boundRect); } return boundRect; } KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const { const int lod = currentLevelOfDetail(); return lod > 0 ? m_d->scheduler.lodNPostExecutionUndoAdapter() : &m_d->postExecutionUndoAdapter; } void KisImage::setUndoStore(KisUndoStore *undoStore) { m_d->legacyUndoAdapter.setUndoStore(undoStore); m_d->postExecutionUndoAdapter.setUndoStore(undoStore); m_d->undoStore.reset(undoStore); } KisUndoStore* KisImage::undoStore() { return m_d->undoStore.data(); } KisUndoAdapter* KisImage::undoAdapter() const { return &m_d->legacyUndoAdapter; } KisActionRecorder* KisImage::actionRecorder() const { return &m_d->recorder; } void KisImage::setDefaultProjectionColor(const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer); m_d->rootLayer->setDefaultProjectionColor(color); } KoColor KisImage::defaultProjectionColor() const { KIS_ASSERT_RECOVER(m_d->rootLayer) { return KoColor(Qt::transparent, m_d->colorSpace); } return m_d->rootLayer->defaultProjectionColor(); } void KisImage::setRootLayer(KisGroupLayerSP rootLayer) { stopIsolatedMode(); KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace); if (m_d->rootLayer) { m_d->rootLayer->setGraphListener(0); m_d->rootLayer->disconnect(); KisPaintDeviceSP original = m_d->rootLayer->original(); defaultProjectionColor = original->defaultPixel(); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); m_d->rootLayer->setImage(this); KisPaintDeviceSP newOriginal = m_d->rootLayer->original(); newOriginal->setDefaultPixel(defaultProjectionColor); setRoot(m_d->rootLayer.data()); } void KisImage::addAnnotation(KisAnnotationSP annotation) { // Find the icc annotation, if there is one vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == annotation->type()) { *it = annotation; return; } ++it; } m_d->annotations.push_back(annotation); } KisAnnotationSP KisImage::annotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { return *it; } ++it; } return KisAnnotationSP(0); } void KisImage::removeAnnotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { m_d->annotations.erase(it); return; } ++it; } } vKisAnnotationSP_it KisImage::beginAnnotations() { return m_d->annotations.begin(); } vKisAnnotationSP_it KisImage::endAnnotations() { return m_d->annotations.end(); } void KisImage::notifyAboutToBeDeleted() { emit sigAboutToBeDeleted(); } KisImageSignalRouter* KisImage::signalRouter() { return &m_d->signalRouter; } void KisImage::waitForDone() { requestStrokeEnd(); m_d->scheduler.waitForDone(); } KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy) { /** * Ask open strokes to end gracefully. All the strokes clients * (including the one calling this method right now) will get * a notification that they should probably end their strokes. * However this is purely their choice whether to end a stroke * or not. */ if (strokeStrategy->requestsOtherStrokesToEnd()) { requestStrokeEnd(); } /** * Some of the strokes can cancel their work with undoing all the * changes they did to the paint devices. The problem is that undo * stack will know nothing about it. Therefore, just notify it * explicitly */ if (strokeStrategy->clearsRedoOnStart()) { m_d->undoStore->purgeRedoState(); } return m_d->scheduler.startStroke(strokeStrategy); } void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc) { KisImageConfig imageConfig; int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < rc.height(); y += patchHeight) { for (int x = 0; x < rc.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); patchRect &= rc; QtConcurrent::run(std::bind(&KisImage::notifyProjectionUpdated, q, patchRect)); } } } bool KisImage::startIsolatedMode(KisNodeSP node) { if (!tryBarrierLock()) return false; unlock(); m_d->isolatedRootNode = node; emit sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_d->notifyProjectionUpdatedInPatches(bounds()); invalidateAllFrames(); return true; } void KisImage::stopIsolatedMode() { if (!m_d->isolatedRootNode) return; KisNodeSP oldRootNode = m_d->isolatedRootNode; m_d->isolatedRootNode = 0; emit sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_d->notifyProjectionUpdatedInPatches(bounds()); invalidateAllFrames(); // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); // oldRootNode->setDirty(updateRect); } KisNodeSP KisImage::isolatedModeRoot() const { return m_d->isolatedRootNode; } void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data) { KisUpdateTimeMonitor::instance()->reportJobStarted(data); m_d->scheduler.addJob(id, data); } void KisImage::endStroke(KisStrokeId id) { m_d->scheduler.endStroke(id); } bool KisImage::cancelStroke(KisStrokeId id) { return m_d->scheduler.cancelStroke(id); } bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync() { return scheduler.tryCancelCurrentStrokeAsync(); } void KisImage::requestUndoDuringStroke() { emit sigUndoDuringStrokeRequested(); } void KisImage::requestStrokeCancellation() { if (!m_d->tryCancelCurrentStrokeAsync()) { emit sigStrokeCancellationRequested(); } } UndoResult KisImage::tryUndoUnfinishedLod0Stroke() { return m_d->scheduler.tryUndoLastStrokeAsync(); } void KisImage::requestStrokeEnd() { emit sigStrokeEndRequested(); emit sigStrokeEndRequestedActiveNodeFiltered(); } void KisImage::requestStrokeEndActiveNode() { emit sigStrokeEndRequested(); } void KisImage::refreshGraph(KisNodeSP root) { refreshGraph(root, bounds(), bounds()); } void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefresh(root, rc, cropRect); } void KisImage::initialRefreshGraph() { /** * NOTE: Tricky part. We set crop rect to null, so the clones * will not rely on precalculated projections of their sources */ refreshGraphAsync(0, bounds(), QRect()); waitForDone(); } void KisImage::refreshGraphAsync(KisNodeSP root) { refreshGraphAsync(root, bounds(), bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc) { refreshGraphAsync(root, rc, bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefreshAsync(root, rc, cropRect); } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect) { KIS_ASSERT_RECOVER_RETURN(pseudoFilthy); m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false); m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect); } void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { m_d->scheduler.addSpontaneousJob(spontaneousJob); } void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) { // udpate filters are *not* recursive! KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter); m_d->projectionUpdatesFilter = filter; } KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const { return m_d->projectionUpdatesFilter; } void KisImage::disableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter())); } void KisImage::enableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP()); } void KisImage::disableUIUpdates() { m_d->disableUIUpdateSignals.ref(); } void KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); } void KisImage::notifyProjectionUpdated(const QRect &rc) { KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc); if (!m_d->disableUIUpdateSignals) { int lod = currentLevelOfDetail(); QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod); if (dirtyRect.isEmpty()) return; emit sigImageUpdated(dirtyRect); } } void KisImage::notifySelectionChanged() { /** * The selection is calculated asynchromously, so it is not * handled by disableUIUpdates() and other special signals of * KisImageSignalRouter */ m_d->legacyUndoAdapter.emitSelectionChanged(); /** * Editing of selection masks doesn't necessary produce a * setDirty() call, so in the end of the stroke we need to request * direct update of the UI's cache. */ if (m_d->isolatedRootNode && dynamic_cast(m_d->isolatedRootNode.data())) { notifyProjectionUpdated(bounds()); } } void KisImage::requestProjectionUpdateImpl(KisNode *node, const QRect &rect, const QRect &cropRect) { if (rect.isEmpty()) return; KisNodeGraphListener::requestProjectionUpdate(node, rect); m_d->scheduler.updateProjection(node, rect, cropRect); } void KisImage::requestProjectionUpdate(KisNode *node, const QRect& rect) { if (m_d->projectionUpdatesFilter && m_d->projectionUpdatesFilter->filter(this, node, rect)) { return; } m_d->animationInterface->notifyNodeChanged(node, rect, false); /** * Here we use 'permitted' instead of 'active' intentively, * because the updates may come after the actual stroke has been * finished. And having some more updates for the stroke not * supporting the wrap-around mode will not make much harm. */ if (m_d->wrapAroundModePermitted) { const QRect boundRect = effectiveLodBounds(); KisWrappedRect splitRect(rect, boundRect); Q_FOREACH (const QRect &rc, splitRect) { requestProjectionUpdateImpl(node, rc, boundRect); } } else { requestProjectionUpdateImpl(node, rect, bounds()); } } void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect) { m_d->animationInterface->invalidateFrames(range, rect); } void KisImage::requestTimeSwitch(int time) { m_d->animationInterface->requestTimeSwitchNonGUI(time); } QList KisImage::compositions() { return m_d->compositions; } void KisImage::addComposition(KisLayerCompositionSP composition) { m_d->compositions.append(composition); } void KisImage::removeComposition(KisLayerCompositionSP composition) { m_d->compositions.removeAll(composition); } bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds) { KisSelectionMask *mask = dynamic_cast(root.data()); if (mask && (!bounds.contains(mask->paintDevice()->exactBounds()) || mask->selection()->hasShapeSelection())) { return true; } KisNodeSP node = root->firstChild(); while (node) { if (checkMasksNeedConversion(node, bounds)) { return true; } node = node->nextSibling(); } return false; } void KisImage::setWrapAroundModePermitted(bool value) { m_d->wrapAroundModePermitted = value; if (m_d->wrapAroundModePermitted && checkMasksNeedConversion(root(), bounds())) { KisProcessingApplicator applicator(this, root(), KisProcessingApplicator::RECURSIVE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Crop Selections")); KisProcessingVisitorSP visitor = new KisCropSelectionsProcessingVisitor(bounds()); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } } bool KisImage::wrapAroundModePermitted() const { return m_d->wrapAroundModePermitted; } bool KisImage::wrapAroundModeActive() const { return m_d->wrapAroundModePermitted && m_d->scheduler.wrapAroundModeSupported(); } void KisImage::setDesiredLevelOfDetail(int lod) { if (m_d->blockLevelOfDetail) { qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()" << "was called while LoD functionality was being blocked!"; return; } m_d->scheduler.setDesiredLevelOfDetail(lod); } int KisImage::currentLevelOfDetail() const { if (m_d->blockLevelOfDetail) { return 0; } return m_d->scheduler.currentLevelOfDetail(); } void KisImage::setLevelOfDetailBlocked(bool value) { KisImageBarrierLockerRaw l(this); if (value && !m_d->blockLevelOfDetail) { m_d->scheduler.setDesiredLevelOfDetail(0); } m_d->blockLevelOfDetail = value; } void KisImage::explicitRegenerateLevelOfDetail() { if (!m_d->blockLevelOfDetail) { m_d->scheduler.explicitRegenerateLevelOfDetail(); } } bool KisImage::levelOfDetailBlocked() const { return m_d->blockLevelOfDetail; } void KisImage::notifyNodeCollpasedChanged() { emit sigNodeCollapsedChanged(); } KisImageAnimationInterface* KisImage::animationInterface() const { return m_d->animationInterface; } void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig) { m_d->proofingConfig = proofingConfig; emit sigProofingConfigChanged(); } KisProofingConfigurationSP KisImage::proofingConfiguration() const { if (!m_d->proofingConfig) { KisImageConfig cfg; m_d->proofingConfig = cfg.defaultProofingconfiguration(); } return m_d->proofingConfig; } QPointF KisImage::mirrorAxesCenter() const { return m_d->axesCenter; } void KisImage::setMirrorAxesCenter(const QPointF &value) const { m_d->axesCenter = value; } diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc index 551d8e0bbf..e39b2d23c8 100644 --- a/libs/image/kis_layer.cc +++ b/libs/image/kis_layer.cc @@ -1,880 +1,902 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_mask.h" #include "kis_effect_mask.h" #include "kis_selection_mask.h" #include "kis_meta_data_store.h" #include "kis_selection.h" #include "kis_paint_layer.h" +#include "kis_raster_keyframe_channel.h" #include "kis_clone_layer.h" #include "kis_psd_layer_style.h" #include "kis_layer_projection_plane.h" #include "layerstyles/kis_layer_style_projection_plane.h" #include "krita_utils.h" #include "kis_layer_properties_icons.h" #include "kis_layer_utils.h" class KisSafeProjection { public: KisPaintDeviceSP getDeviceLazy(KisPaintDeviceSP prototype) { QMutexLocker locker(&m_lock); if (!m_reusablePaintDevice) { m_reusablePaintDevice = new KisPaintDevice(*prototype); } if(!m_projection || *m_projection->colorSpace() != *prototype->colorSpace()) { m_projection = m_reusablePaintDevice; m_projection->makeCloneFromRough(prototype, prototype->extent()); m_projection->setProjectionDevice(true); } return m_projection; } void freeDevice() { QMutexLocker locker(&m_lock); m_projection = 0; if(m_reusablePaintDevice) { m_reusablePaintDevice->clear(); } } private: QMutex m_lock; KisPaintDeviceSP m_projection; KisPaintDeviceSP m_reusablePaintDevice; }; class KisCloneLayersList { public: void addClone(KisCloneLayerWSP cloneLayer) { m_clonesList.append(cloneLayer); } void removeClone(KisCloneLayerWSP cloneLayer) { m_clonesList.removeOne(cloneLayer); } void setDirty(const QRect &rect) { Q_FOREACH (KisCloneLayerSP clone, m_clonesList) { if (clone) { clone->setDirtyOriginal(rect); } } } const QList registeredClones() const { return m_clonesList; } bool hasClones() const { return !m_clonesList.isEmpty(); } private: QList m_clonesList; }; struct Q_DECL_HIDDEN KisLayer::Private { KisImageWSP image; QBitArray channelFlags; KisMetaData::Store* metaDataStore; KisSafeProjection safeProjection; KisCloneLayersList clonesList; KisPSDLayerStyleSP layerStyle; KisAbstractProjectionPlaneSP layerStyleProjectionPlane; KisAbstractProjectionPlaneSP projectionPlane; }; KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisNode() , m_d(new Private) { setName(name); setOpacity(opacity); m_d->image = image; m_d->metaDataStore = new KisMetaData::Store(); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); } KisLayer::KisLayer(const KisLayer& rhs) : KisNode(rhs) , m_d(new Private()) { if (this != &rhs) { m_d->image = rhs.m_d->image; m_d->metaDataStore = new KisMetaData::Store(*rhs.m_d->metaDataStore); m_d->channelFlags = rhs.m_d->channelFlags; setName(rhs.name()); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); if (rhs.m_d->layerStyle) { setLayerStyle(rhs.m_d->layerStyle->clone()); } } } KisLayer::~KisLayer() { delete m_d->metaDataStore; delete m_d; } const KoColorSpace * KisLayer::colorSpace() const { KisImageSP image = m_d->image.toStrongRef(); if (!image) { return nullptr; } return image->colorSpace(); } const KoCompositeOp * KisLayer::compositeOp() const { /** * FIXME: This function duplicates the same function from * KisMask. We can't move it to KisBaseNode as it doesn't * know anything about parent() method of KisNode * Please think it over... */ KisNodeSP parentNode = parent(); if (!parentNode) return 0; if (!parentNode->colorSpace()) return 0; const KoCompositeOp* op = parentNode->colorSpace()->compositeOp(compositeOpId()); return op ? op : parentNode->colorSpace()->compositeOp(COMPOSITE_OVER); } KisPSDLayerStyleSP KisLayer::layerStyle() const { return m_d->layerStyle; } void KisLayer::setLayerStyle(KisPSDLayerStyleSP layerStyle) { if (layerStyle) { m_d->layerStyle = layerStyle; KisAbstractProjectionPlaneSP plane = !layerStyle->isEmpty() ? KisAbstractProjectionPlaneSP(new KisLayerStyleProjectionPlane(this)) : KisAbstractProjectionPlaneSP(0); m_d->layerStyleProjectionPlane = plane; } else { m_d->layerStyleProjectionPlane.clear(); m_d->layerStyle.clear(); } } KisBaseNode::PropertyList KisLayer::sectionModelProperties() const { KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties(); l << KisBaseNode::Property(KoID("opacity", i18n("Opacity")), i18n("%1%", percentOpacity())); const KoCompositeOp * compositeOp = this->compositeOp(); if (compositeOp) { l << KisBaseNode::Property(KoID("compositeop", i18n("Composite Mode")), compositeOp->description()); } if (m_d->layerStyle && !m_d->layerStyle->isEmpty()) { l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::layerStyle, m_d->layerStyle->isEnabled()); } l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::inheritAlpha, alphaChannelDisabled()); return l; } void KisLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisBaseNode::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id == KisLayerPropertiesIcons::inheritAlpha.id()) { disableAlphaChannel(property.state.toBool()); } if (property.id == KisLayerPropertiesIcons::layerStyle.id()) { if (m_d->layerStyle && m_d->layerStyle->isEnabled() != property.state.toBool()) { m_d->layerStyle->setEnabled(property.state.toBool()); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } } } void KisLayer::disableAlphaChannel(bool disable) { QBitArray newChannelFlags = m_d->channelFlags; if(newChannelFlags.isEmpty()) newChannelFlags = colorSpace()->channelFlags(true, true); if(disable) newChannelFlags &= colorSpace()->channelFlags(true, false); else newChannelFlags |= colorSpace()->channelFlags(false, true); setChannelFlags(newChannelFlags); } bool KisLayer::alphaChannelDisabled() const { QBitArray flags = colorSpace()->channelFlags(false, true) & m_d->channelFlags; return flags.count(true) == 0 && !m_d->channelFlags.isEmpty(); } void KisLayer::setChannelFlags(const QBitArray & channelFlags) { Q_ASSERT(channelFlags.isEmpty() ||((quint32)channelFlags.count() == colorSpace()->channelCount())); if (KritaUtils::compareChannelFlags(channelFlags, this->channelFlags())) { return; } if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { m_d->channelFlags.clear(); } else { m_d->channelFlags = channelFlags; } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } QBitArray & KisLayer::channelFlags() const { return m_d->channelFlags; } bool KisLayer::temporary() const { return nodeProperties().boolProperty("temporary", false); } void KisLayer::setTemporary(bool t) { nodeProperties().setProperty("temporary", t); } KisImageWSP KisLayer::image() const { return m_d->image; } void KisLayer::setImage(KisImageWSP image) { m_d->image = image; KisNodeSP node = firstChild(); while (node) { KisLayerUtils::recursiveApplyNodes(node, [image] (KisNodeSP node) { node->setImage(image); }); node = node->nextSibling(); } } bool KisLayer::canMergeAndKeepBlendOptions(KisLayerSP otherLayer) { return this->compositeOpId() == otherLayer->compositeOpId() && this->opacity() == otherLayer->opacity() && this->channelFlags() == otherLayer->channelFlags() && !this->layerStyle() && !otherLayer->layerStyle() && (this->colorSpace() == otherLayer->colorSpace() || *this->colorSpace() == *otherLayer->colorSpace()); } KisLayerSP KisLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); KisLayerSP newLayer = new KisPaintLayer(image(), prevLayer->name(), OPACITY_OPAQUE_U8); if (keepBlendingOptions) { newLayer->setCompositeOpId(compositeOpId()); newLayer->setOpacity(opacity()); newLayer->setChannelFlags(channelFlags()); } return newLayer; } void KisLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); QRect layerProjectionExtent = this->projection()->extent(); QRect prevLayerProjectionExtent = prevLayer->projection()->extent(); bool alphaDisabled = this->alphaChannelDisabled(); bool prevAlphaDisabled = prevLayer->alphaChannelDisabled(); KisPaintDeviceSP mergedDevice = dstLayer->paintDevice(); if (!keepBlendingOptions) { KisPainter gc(mergedDevice); KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } //Copy the pixels of previous layer with their actual alpha value prevLayer->disableAlphaChannel(false); prevLayer->projectionPlane()->apply(&gc, prevLayerProjectionExtent | imageSP->bounds()); //Restore the previous prevLayer disableAlpha status for correct undo/redo prevLayer->disableAlphaChannel(prevAlphaDisabled); //Paint the pixels of the current layer, using their actual alpha value if (alphaDisabled == prevAlphaDisabled) { this->disableAlphaChannel(false); } this->projectionPlane()->apply(&gc, layerProjectionExtent | imageSP->bounds()); //Restore the layer disableAlpha status for correct undo/redo this->disableAlphaChannel(alphaDisabled); } else { //Copy prevLayer KisPaintDeviceSP srcDev = prevLayer->projection(); mergedDevice->makeCloneFrom(srcDev, srcDev->extent()); //Paint layer on the copy KisPainter gc(mergedDevice); gc.bitBlt(layerProjectionExtent.topLeft(), this->projection(), layerProjectionExtent); } } void KisLayer::registerClone(KisCloneLayerWSP clone) { m_d->clonesList.addClone(clone); } void KisLayer::unregisterClone(KisCloneLayerWSP clone) { m_d->clonesList.removeClone(clone); } const QList KisLayer::registeredClones() const { return m_d->clonesList.registeredClones(); } bool KisLayer::hasClones() const { return m_d->clonesList.hasClones(); } void KisLayer::updateClones(const QRect &rect) { m_d->clonesList.setDirty(rect); } KisSelectionMaskSP KisLayer::selectionMask() const { KoProperties properties; properties.setProperty("active", true); QList masks = childNodes(QStringList("KisSelectionMask"), properties); // return the first visible mask Q_FOREACH (KisNodeSP mask, masks) { if (mask->visible()) { return dynamic_cast(mask.data()); } } return KisSelectionMaskSP(); } KisSelectionSP KisLayer::selection() const { KisSelectionMaskSP mask = selectionMask(); if (mask) { return mask->selection(); } KisImageSP image = m_d->image.toStrongRef(); if (image) { return image->globalSelection(); } return KisSelectionSP(); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// QList KisLayer::effectMasks(KisNodeSP lastNode) const { QList masks; if (childCount() > 0) { KoProperties properties; properties.setProperty("visible", true); QList nodes = childNodes(QStringList("KisEffectMask"), properties); Q_FOREACH (const KisNodeSP& node, nodes) { if (node == lastNode) break; KisEffectMaskSP mask = dynamic_cast(const_cast(node.data())); if (mask) masks.append(mask); } } return masks; } bool KisLayer::hasEffectMasks() const { if (childCount() == 0) return false; KisNodeSP node = firstChild(); while (node) { if (node->inherits("KisEffectMask") && node->visible()) { return true; } node = node->nextSibling(); } return false; } QRect KisLayer::masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevChangeRect = requestedRect; /** * We set default value of the change rect for the case * when there is no mask at all */ QRect changeRect = requestedRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { changeRect = mask->changeRect(prevChangeRect); if (changeRect != prevChangeRect) rectVariesFlag = true; prevChangeRect = changeRect; } return changeRect; } QRect KisLayer::masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevNeedRect = changeRect; QRect needRect; for (qint32 i = masks.size() - 1; i >= 0; i--) { applyRects.push(prevNeedRect); needRect = masks[i]->needRect(prevNeedRect); if (prevNeedRect != needRect) rectVariesFlag = true; prevNeedRect = needRect; } return needRect; } KisNode::PositionToFilthy calculatePositionToFilthy(KisNodeSP nodeInQuestion, KisNodeSP filthy, KisNodeSP parent) { if (parent == filthy || parent != filthy->parent()) { return KisNode::N_ABOVE_FILTHY; } if (nodeInQuestion == filthy) { return KisNode::N_FILTHY; } KisNodeSP node = nodeInQuestion->prevSibling(); while (node) { if (node == filthy) { return KisNode::N_ABOVE_FILTHY; } node = node->prevSibling(); } return KisNode::N_BELOW_FILTHY; } QRect KisLayer::applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const { Q_ASSERT(source); Q_ASSERT(destination); QList masks = effectMasks(lastNode); QRect changeRect; QRect needRect; if (masks.isEmpty()) { changeRect = requestedRect; if (source != destination) { copyOriginalToProjection(source, destination, requestedRect); } } else { QStack applyRects; bool changeRectVaries; bool needRectVaries; /** * FIXME: Assume that varying of the changeRect has already * been taken into account while preparing walkers */ changeRectVaries = false; changeRect = requestedRect; //changeRect = masksChangeRect(masks, requestedRect, // changeRectVaries); needRect = masksNeedRect(masks, changeRect, applyRects, needRectVaries); if (!changeRectVaries && !needRectVaries) { /** * A bit of optimization: * All filters will read/write exactly from/to the requested * rect so we needn't create temporary paint device, * just apply it onto destination */ Q_ASSERT(needRect == requestedRect); if (source != destination) { copyOriginalToProjection(source, destination, needRect); } Q_FOREACH (const KisEffectMaskSP& mask, masks) { const QRect maskApplyRect = applyRects.pop(); const QRect maskNeedRect = applyRects.isEmpty() ? needRect : applyRects.top(); PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(destination, maskApplyRect, maskNeedRect, maskPosition); } Q_ASSERT(applyRects.isEmpty()); } else { /** * We can't eliminate additional copy-op * as filters' behaviour may be quite insane here, * so let them work on their own paintDevice =) */ KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace()); tempDevice->prepareClone(source); copyOriginalToProjection(source, tempDevice, needRect); QRect maskApplyRect = applyRects.pop(); QRect maskNeedRect = needRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(tempDevice, maskApplyRect, maskNeedRect, maskPosition); if (!applyRects.isEmpty()) { maskNeedRect = maskApplyRect; maskApplyRect = applyRects.pop(); } } Q_ASSERT(applyRects.isEmpty()); KisPainter::copyAreaOptimized(changeRect.topLeft(), tempDevice, destination, changeRect); } } return changeRect; } QRect KisLayer::updateProjection(const QRect& rect, KisNodeSP filthyNode) { QRect updatedRect = rect; KisPaintDeviceSP originalDevice = original(); if (!rect.isValid() || !visible() || !originalDevice) return QRect(); if (!needProjection() && !hasEffectMasks()) { m_d->safeProjection.freeDevice(); } else { if (!updatedRect.isEmpty()) { KisPaintDeviceSP projection = m_d->safeProjection.getDeviceLazy(originalDevice); updatedRect = applyMasks(originalDevice, projection, updatedRect, filthyNode, 0); } } return updatedRect; } QRect KisLayer::partialChangeRect(KisNodeSP lastNode, const QRect& rect) { bool changeRectVaries = false; QRect changeRect = outgoingChangeRect(rect); changeRect = masksChangeRect(effectMasks(lastNode), changeRect, changeRectVaries); return changeRect; } /** * \p rect is a dirty rect in layer's original() coordinates! */ void KisLayer::buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect) { QRect changeRect = partialChangeRect(lastNode, rect); KisPaintDeviceSP originalDevice = original(); KIS_ASSERT_RECOVER_RETURN(needProjection() || hasEffectMasks()); if (!changeRect.isEmpty()) { applyMasks(originalDevice, projection, changeRect, this, lastNode); } } bool KisLayer::needProjection() const { return false; } void KisLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect); } KisAbstractProjectionPlaneSP KisLayer::projectionPlane() const { return m_d->layerStyleProjectionPlane ? m_d->layerStyleProjectionPlane : m_d->projectionPlane; } KisAbstractProjectionPlaneSP KisLayer::internalProjectionPlane() const { return m_d->projectionPlane; } KisPaintDeviceSP KisLayer::projection() const { KisPaintDeviceSP originalDevice = original(); return needProjection() || hasEffectMasks() ? m_d->safeProjection.getDeviceLazy(originalDevice) : originalDevice; } QRect KisLayer::changeRect(const QRect &rect, PositionToFilthy pos) const { QRect changeRect = rect; changeRect = incomingChangeRect(changeRect); if(pos == KisNode::N_FILTHY) { QRect projectionToBeUpdated = projection()->exactBoundsAmortized() & changeRect; bool changeRectVaries; changeRect = outgoingChangeRect(changeRect); changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries); /** * If the projection contains some dirty areas we should also * add them to the change rect, because they might have * changed. E.g. when a visibility of the mask has chnaged * while the parent layer was invinisble. */ if (!projectionToBeUpdated.isEmpty() && !changeRect.contains(projectionToBeUpdated)) { changeRect |= projectionToBeUpdated; } } // TODO: string comparizon: optimize! if (pos != KisNode::N_FILTHY && pos != KisNode::N_FILTHY_PROJECTION && compositeOpId() != COMPOSITE_COPY) { changeRect |= rect; } return changeRect; } QRect KisLayer::incomingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::outgoingChangeRect(const QRect &rect) const { return rect; } QImage KisLayer::createThumbnail(qint32 w, qint32 h) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } +QImage KisLayer::createThumbnailForFrame(qint32 w, qint32 h, int time) +{ + if (w == 0 || h == 0) { + return QImage(); + } + + KisPaintDeviceSP originalDevice = original(); + if (originalDevice) { + KisPaintDeviceSP targetDevice = new KisPaintDevice(colorSpace()); + KisRasterKeyframeChannel *channel = originalDevice->keyframeChannel(); + KisKeyframeSP keyframe = channel->activeKeyframeAt(time); + channel->fetchFrame(keyframe, targetDevice); + return targetDevice->createThumbnail(w, h, 1, + KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::internalConversionFlags()); + } + else { + return QImage(); + } +} + qint32 KisLayer::x() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->x() : 0; } qint32 KisLayer::y() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->y() : 0; } void KisLayer::setX(qint32 x) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setX(x); } void KisLayer::setY(qint32 y) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setY(y); } QRect KisLayer::layerExtentImpl(bool needExactBounds) const { QRect additionalMaskExtent = QRect(); QList effectMasks = this->effectMasks(); Q_FOREACH(KisEffectMaskSP mask, effectMasks) { additionalMaskExtent |= mask->nonDependentExtent(); } KisPaintDeviceSP originalDevice = original(); QRect layerExtent; if (originalDevice) { layerExtent = needExactBounds ? originalDevice->exactBounds() : originalDevice->extent(); } QRect additionalCompositeOpExtent; if (compositeOpId() == COMPOSITE_DESTINATION_IN || compositeOpId() == COMPOSITE_DESTINATION_ATOP) { additionalCompositeOpExtent = originalDevice->defaultBounds()->bounds(); } return layerExtent | additionalMaskExtent | additionalCompositeOpExtent; } QRect KisLayer::extent() const { return layerExtentImpl(false); } QRect KisLayer::exactBounds() const { return layerExtentImpl(true); } KisLayerSP KisLayer::parentLayer() const { return qobject_cast(parent().data()); } KisMetaData::Store* KisLayer::metaData() { return m_d->metaDataStore; } diff --git a/libs/image/kis_layer.h b/libs/image/kis_layer.h index 4515b297d4..6829386e7d 100644 --- a/libs/image/kis_layer.h +++ b/libs/image/kis_layer.h @@ -1,387 +1,389 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_LAYER_H_ #define KIS_LAYER_H_ #include #include #include #include #include "kritaimage_export.h" #include "kis_base_node.h" #include "kis_types.h" #include "kis_node.h" #include "kis_psd_layer_style.h" template class QStack; class QBitArray; class KisCloneLayer; class KisPSDLayerStyle; class KisAbstractProjectionPlane; namespace KisMetaData { class Store; } /** * Abstract class that represents the concept of a Layer in Krita. This is not related * to the paint devices: this is merely an abstraction of how layers can be stacked and * rendered differently. * Regarding the previous-, first-, next- and lastChild() calls, first means that it the layer * is at the top of the group in the layerlist, using next will iterate to the bottom to last, * whereas previous will go up to first again. * * * TODO: Add a layer mode whereby the projection of the layer is used * as a clipping path? **/ class KRITAIMAGE_EXPORT KisLayer : public KisNode { Q_OBJECT public: /** * @param image is the pointer of the image or null * @param opacity is a value between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 **/ KisLayer(KisImageWSP image, const QString &name, quint8 opacity); KisLayer(const KisLayer& rhs); virtual ~KisLayer(); /// returns the image's colorSpace or null, if there is no image virtual const KoColorSpace * colorSpace() const override; /// returns the layer's composite op for the colorspace of the layer's parent. const KoCompositeOp * compositeOp() const override; KisPSDLayerStyleSP layerStyle() const; void setLayerStyle(KisPSDLayerStyleSP layerStyle); /** * \see a comment in KisNode::projectionPlane() */ virtual KisAbstractProjectionPlaneSP projectionPlane() const override; /** * The projection plane representing the layer itself without any * styles or anything else. It is used by the layer styles projection * plane to stack up the planes. */ virtual KisAbstractProjectionPlaneSP internalProjectionPlane() const; QRect partialChangeRect(KisNodeSP lastNode, const QRect& rect); void buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect); virtual bool needProjection() const; /** * Return the fully rendered representation of this layer: its * data and its effect masks */ KisPaintDeviceSP projection() const override; /** * Return the layer data before the effect masks have had their go * at it. */ virtual KisPaintDeviceSP original() const override = 0; /** * @return the selection associated with this layer, if there is * one. Otherwise, return 0; */ virtual KisSelectionMaskSP selectionMask() const; /** * @return the selection contained in the first KisSelectionMask associated * with this layer or the image, if either exists, otherwise, return 0. */ virtual KisSelectionSP selection() const; virtual KisBaseNode::PropertyList sectionModelProperties() const override; virtual void setSectionModelProperties(const KisBaseNode::PropertyList &properties) override; /** * set/unset the channel flag for the alpha channel of this layer */ void disableAlphaChannel(bool disable); /** * returns true if the channel flag for the alpha channel * of this layer is not set. * returns false otherwise. */ bool alphaChannelDisabled() const; /** * set the channelflags for this layer to the specified bit array. * The bit array must have exactly the same number of channels as * the colorspace this layer is in, or be empty, in which case all * channels are active. */ virtual void setChannelFlags(const QBitArray & channelFlags); /** * Return a bit array where each bit indicates whether a * particular channel is active or not. If the channelflags bit * array is empty, all channels are active. */ QBitArray & channelFlags() const; /** * Returns true if this layer is temporary: i.e., it should not * appear in the layerbox, even though it is temporarily in the * layer stack and taken into account on recomposition. */ bool temporary() const; /** * Set to true if this layer should not appear in the layerbox, * even though it is temporarily in the layer stack and taken into * account on recomposition. */ void setTemporary(bool t); /// returns the image this layer belongs to, or null if there is no image KisImageWSP image() const; /** * Set the image this layer belongs to. */ void setImage(KisImageWSP image) override; /** * Create and return a layer that is the result of merging * this with layer. * * This method is designed to be called only within KisImage::mergeLayerDown(). * * Decendands override this to create specific merged types when possible. * The KisLayer one creates a KisPaintLayerSP via a bitBlt, and can work on all layer types. * * Decendants that perform there own version do NOT call KisLayer::createMergedLayer */ virtual KisLayerSP createMergedLayerTemplate(KisLayerSP prevLayer); virtual void fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer); /** * Clones should be informed about updates of the original * layer, so this is a way to register them */ void registerClone(KisCloneLayerWSP clone); /** * Deregisters the clone from the update list * * \see registerClone() */ void unregisterClone(KisCloneLayerWSP clone); /** * Return the list of the clones of this node. Be careful * with the list, because it is not thread safe. */ const QList registeredClones() const; /** * Returns whether we have a clone. * * Be careful with it. It is not thread safe to add/remove * clone while checking hasClones(). So there should be no updates. */ bool hasClones() const; /** * It is calles by the async merger after projection update is done */ void updateClones(const QRect &rect); public: qint32 x() const override; qint32 y() const override; void setX(qint32 x) override; void setY(qint32 y) override; /** * Returns an approximation of where the bounds * of actual data of this layer are */ QRect extent() const override; /** * Returns the exact bounds of where the actual data * of this layer resides */ QRect exactBounds() const override; QImage createThumbnail(qint32 w, qint32 h) override; + QImage createThumbnailForFrame(qint32 w, qint32 h, int time) override; + public: /** * Returns true if there are any effect masks present */ bool hasEffectMasks() const; /** * @return the list of effect masks */ QList effectMasks(KisNodeSP lastNode = KisNodeSP()) const; /** * Get the group layer that contains this layer. */ KisLayerSP parentLayer() const; /** * @return the metadata object associated with this object. */ KisMetaData::Store* metaData(); protected: // override from KisNode QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; protected: /** * Ask the layer to assemble its data & apply all the effect masks * to it. */ QRect updateProjection(const QRect& rect, KisNodeSP filthyNode); /** * Layers can override this method to get some special behavior * when copying data from \p original to \p projection, e.g. blend * in indirect painting device. If you need to modify data * outside \p rect, please also override outgoingChangeRect() * method. */ virtual void copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const; /** * For KisLayer classes change rect transformation consists of two * parts: incoming and outgoing. * * 1) incomingChangeRect(rect) chande rect transformation * performed by the transformations done basing on global * projection. It is performed in KisAsyncMerger + * KisUpdateOriginalVisitor classes. It happens before data * coming to KisLayer::original() therefore it is * 'incoming'. See KisAdjustmentLayer for example of usage. * * 2) outgoingChangeRect(rect) change rect transformation that * happens in KisLayer::copyOriginalToProjection(). It applies * *only* when the layer is 'filthy', that is was the cause of * the merge process. See KisCloneLayer for example of usage. * * The flow of changed areas can be illustrated in the * following way: * * 1. Current projection of size R1 is stored in KisAsyncMerger::m_currentProjection * | * | <-- KisUpdateOriginalVisitor writes data into layer's original() device. * | The changed area on KisLayer::original() is * | R2 = KisLayer::incomingChangeRect(R1) * | * 2. KisLayer::original() / changed rect: R2 * | * | <-- KisLayer::updateProjection() starts composing a layer * | It calls KisLayer::copyOriginalToProjection() which copies some area * | to a temporaty device. The temporary device now stores * | R3 = KisLayer::outgoingChangeRect(R2) * | * 3. Temporary device / changed rect: R3 * | * | <-- KisLayer::updateProjection() continues composing a layer. It merges a mask. * | R4 = KisMask::changeRect(R3) * | * 4. KisLayer::original() / changed rect: R4 * * So in the end rect R4 will be passed up to the next layers in the stack. */ virtual QRect incomingChangeRect(const QRect &rect) const; /** * \see incomingChangeRect() */ virtual QRect outgoingChangeRect(const QRect &rect) const; /** * @param rectVariesFlag (out param) a flag, showing whether * a rect varies from mask to mask * @return an area that should be updated because of * the change of @requestedRect of the layer */ QRect masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const; /** * Get needRects for all masks * @param changeRect requested rect to be updated on final * projection. Should be a return value * of @ref masksChangedRect() * @param applyRects (out param) a stack of the rects where filters * should be applied * @param rectVariesFlag (out param) a flag, showing whether * a rect varies from mask to mask * @return a needRect that should be prepared on the layer's * paintDevice for all masks to succeed */ QRect masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const; QRect applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const; bool canMergeAndKeepBlendOptions(KisLayerSP otherLayer); private: friend class KisLayerProjectionPlane; friend class KisTransformMask; friend class KisLayerTest; private: QRect layerExtentImpl(bool exactBounds) const; private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE(KisLayerSP) #endif // KIS_LAYER_H_ diff --git a/libs/image/kis_properties_configuration.cc b/libs/image/kis_properties_configuration.cc index 22ae5e418a..e5a6baddf3 100644 --- a/libs/image/kis_properties_configuration.cc +++ b/libs/image/kis_properties_configuration.cc @@ -1,319 +1,319 @@ /* * Copyright (c) 2006 Boudewijn Rempt * Copyright (c) 2007,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_properties_configuration.h" #include #include #include #include "kis_image.h" #include "kis_transaction.h" #include "kis_undo_adapter.h" #include "kis_painter.h" #include "kis_selection.h" #include "KoID.h" #include "kis_types.h" #include #include struct Q_DECL_HIDDEN KisPropertiesConfiguration::Private { QMap properties; QStringList notSavedProperties; }; KisPropertiesConfiguration::KisPropertiesConfiguration() : d(new Private) { } KisPropertiesConfiguration::~KisPropertiesConfiguration() { delete d; } KisPropertiesConfiguration::KisPropertiesConfiguration(const KisPropertiesConfiguration& rhs) : KisSerializableConfiguration(rhs) , d(new Private(*rhs.d)) { } bool KisPropertiesConfiguration::fromXML(const QString & xml, bool clear) { if (clear) { clearProperties(); } QDomDocument doc; bool retval = doc.setContent(xml); if (retval) { QDomElement e = doc.documentElement(); fromXML(e); } return retval; } void KisPropertiesConfiguration::fromXML(const QDomElement& e) { QDomNode n = e.firstChild(); while (!n.isNull()) { // We don't nest elements in filter configuration. For now... QDomElement e = n.toElement(); if (!e.isNull()) { if (e.tagName() == "param") { // If the file contains the new type parameter introduced in Krita act on it // Else invoke old behaviour if(e.attributes().contains("type")) { QString type = e.attribute("type"); QString name = e.attribute("name"); QString value = e.text(); if(type == "bytearray") { d->properties[name] = QVariant(QByteArray::fromBase64(value.toLatin1())); } else d->properties[name] = value; } else d->properties[e.attribute("name")] = QVariant(e.text()); } } n = n.nextSibling(); } //dump(); } void KisPropertiesConfiguration::toXML(QDomDocument& doc, QDomElement& root) const { QMap::Iterator it; for (it = d->properties.begin(); it != d->properties.end(); ++it) { if(d->notSavedProperties.contains(it.key())) { continue; } QDomElement e = doc.createElement("param"); e.setAttribute("name", QString(it.key().toLatin1())); QString type = "string"; QVariant v = it.value(); QDomText text; if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { text = doc.createCDATASection(v.value().toString()); } else if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { QDomDocument doc = QDomDocument("color"); QDomElement root = doc.createElement("color"); doc.appendChild(root); v.value().toXML(doc, root); text = doc.createCDATASection(doc.toString()); type = "color"; } else if(v.type() == QVariant::String ) { text = doc.createCDATASection(v.toString()); // XXX: Unittest this! type = "string"; } else if(v.type() == QVariant::ByteArray ) { text = doc.createTextNode(QString::fromLatin1(v.toByteArray().toBase64())); // Arbitrary Data type = "bytearray"; } else { text = doc.createTextNode(v.toString()); type = "internal"; } e.setAttribute("type", type); e.appendChild(text); root.appendChild(e); } } QString KisPropertiesConfiguration::toXML() const { QDomDocument doc = QDomDocument("params"); QDomElement root = doc.createElement("params"); doc.appendChild(root); toXML(doc, root); return doc.toString(); } bool KisPropertiesConfiguration::hasProperty(const QString& name) const { return d->properties.contains(name); } void KisPropertiesConfiguration::setProperty(const QString & name, const QVariant & value) { if (d->properties.find(name) == d->properties.end()) { d->properties.insert(name, value); } else { d->properties[name] = value; } } bool KisPropertiesConfiguration::getProperty(const QString & name, QVariant & value) const { if (d->properties.find(name) == d->properties.end()) { return false; } else { value = d->properties[name]; return true; } } QVariant KisPropertiesConfiguration::getProperty(const QString & name) const { if (d->properties.find(name) == d->properties.end()) { return QVariant(); } else { return d->properties[name]; } } int KisPropertiesConfiguration::getInt(const QString & name, int def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toInt(); else return def; } double KisPropertiesConfiguration::getDouble(const QString & name, double def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toDouble(); else return def; } float KisPropertiesConfiguration::getFloat(const QString & name, float def) const { QVariant v = getProperty(name); if (v.isValid()) return (float)v.toDouble(); else return def; } bool KisPropertiesConfiguration::getBool(const QString & name, bool def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toBool(); else return def; } QString KisPropertiesConfiguration::getString(const QString & name, const QString & def) const { QVariant v = getProperty(name); if (v.isValid()) return v.toString(); else return def; } KisCubicCurve KisPropertiesConfiguration::getCubicCurve(const QString & name, const KisCubicCurve & curve) const { QVariant v = getProperty(name); if (v.isValid()) { if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { return v.value(); } else { KisCubicCurve c; c.fromString(v.toString()); return c; } } else return curve; } KoColor KisPropertiesConfiguration::getColor(const QString& name, const KoColor& color) const { QVariant v = getProperty(name); if (v.isValid()) { if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId()) { return v.value(); } else { QDomDocument doc; doc.setContent(v.toString()); QDomElement e = doc.documentElement().firstChild().toElement(); - return KoColor::fromXML(e, Integer16BitsColorDepthID.id(), QHash()); + return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } } else { return color; } } void KisPropertiesConfiguration::dump() const { QMap::Iterator it; for (it = d->properties.begin(); it != d->properties.end(); ++it) { dbgKrita << it.key() << " = " << it.value(); } } void KisPropertiesConfiguration::clearProperties() { d->properties.clear(); } void KisPropertiesConfiguration::setPropertyNotSaved(const QString& name) { d->notSavedProperties.append(name); } QMap KisPropertiesConfiguration::getProperties() const { return d->properties; } void KisPropertiesConfiguration::removeProperty(const QString & name) { d->properties.remove(name); } // --- factory --- struct Q_DECL_HIDDEN KisPropertiesConfigurationFactory::Private { }; KisPropertiesConfigurationFactory::KisPropertiesConfigurationFactory() : d(new Private) { } KisPropertiesConfigurationFactory::~KisPropertiesConfigurationFactory() { delete d; } KisSerializableConfigurationSP KisPropertiesConfigurationFactory::createDefault() { return new KisPropertiesConfiguration(); } KisSerializableConfigurationSP KisPropertiesConfigurationFactory::create(const QDomElement& e) { KisPropertiesConfigurationSP pc = new KisPropertiesConfiguration(); pc->fromXML(e); return pc; } diff --git a/libs/image/recorder/kis_recorded_paint_action.cpp b/libs/image/recorder/kis_recorded_paint_action.cpp index 2a54566646..5f04f12294 100644 --- a/libs/image/recorder/kis_recorded_paint_action.cpp +++ b/libs/image/recorder/kis_recorded_paint_action.cpp @@ -1,467 +1,467 @@ /* * Copyright (c) 2007,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include //MSVC requires that Vc come first #include "recorder/kis_recorded_paint_action.h" #include #include #include #include #include #include #include "kis_node.h" #include "kis_mask_generator.h" #include "kis_painter.h" #include #include "kis_paintop_registry.h" #include "kis_transaction.h" #include "kis_undo_adapter.h" #include #include #include "kis_paint_device.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_play_info.h" #include "kis_node_query_path.h" #include // Recorder #include "kis_recorded_action_factory_registry.h" #include "kis_recorded_action_load_context.h" #include "kis_recorded_action_save_context.h" #include #include #include #include #include struct Q_DECL_HIDDEN KisRecordedPaintAction::Private { KisPaintOpPresetSP paintOpPreset; KoColor foregroundColor; KoColor backgroundColor; qreal opacity; ///< opacity in the range 0.0 -> 100.0 bool paintIncremental; QString compositeOp; KisPainter::StrokeStyle strokeStyle; KisPainter::FillStyle fillStyle; const KoPattern* pattern; const KoAbstractGradient* gradient; KisFilterConfigurationSP generator; }; KisRecordedPaintAction::KisRecordedPaintAction(const QString & id, const QString & name, const KisNodeQueryPath& path, const KisPaintOpPresetSP paintOpPreset) : KisRecordedNodeAction(id, name, path) , d(new Private) { if (paintOpPreset) { d->paintOpPreset = paintOpPreset; } d->opacity = 1.0; d->paintIncremental = true; d->compositeOp = COMPOSITE_OVER; d->strokeStyle = KisPainter::StrokeStyleBrush; d->fillStyle = KisPainter::FillStyleNone; d->pattern = 0; d->gradient = 0; } KisRecordedPaintAction::KisRecordedPaintAction(const KisRecordedPaintAction& rhs) : KisRecordedNodeAction(rhs), d(new Private(*rhs.d)) { } KisRecordedPaintAction::~KisRecordedPaintAction() { delete d; } void KisRecordedPaintAction::toXML(QDomDocument& doc, QDomElement& elt, KisRecordedActionSaveContext* context) const { KisRecordedAction::toXML(doc, elt, context); // Paint op presset if (d->paintOpPreset) { QDomElement paintopPressetElt = doc.createElement("PaintopPreset"); d->paintOpPreset->toXML(doc, paintopPressetElt); elt.appendChild(paintopPressetElt); } // ForegroundColor QDomElement foregroundColorElt = doc.createElement("ForegroundColor"); d->foregroundColor.toXML(doc, foregroundColorElt); elt.appendChild(foregroundColorElt); // BackgroundColor QDomElement backgroundColorElt = doc.createElement("BackgroundColor"); d->backgroundColor.toXML(doc, backgroundColorElt); elt.appendChild(backgroundColorElt); // Opacity elt.setAttribute("opacity", KisDomUtils::toString(d->opacity)); // paintIncremental elt.setAttribute("paintIncremental", d->paintIncremental); // compositeOp elt.setAttribute("compositeOp", d->compositeOp); // Save stroke style switch(d->strokeStyle) { case KisPainter::StrokeStyleNone: elt.setAttribute("strokeStyle", "None"); break; case KisPainter::StrokeStyleBrush: elt.setAttribute("strokeStyle", "Brush"); break; } // Save fill style switch(d->fillStyle) { case KisPainter::FillStyleNone: elt.setAttribute("fillStyle", "None"); break; case KisPainter::FillStyleForegroundColor: elt.setAttribute("fillStyle", "PaintColor"); break; case KisPainter::FillStyleBackgroundColor: elt.setAttribute("fillStyle", "AlternativeColor"); break; case KisPainter::FillStylePattern: elt.setAttribute("fillStyle", "Pattern"); context->savePattern(d->pattern); elt.setAttribute("pattern", d->pattern->name()); break; case KisPainter::FillStyleGradient: elt.setAttribute("fillStyle", "Gradient"); context->saveGradient(d->gradient); elt.setAttribute("gradient", d->gradient->name()); break; case KisPainter::FillStyleStrokes: elt.setAttribute("fillStyle", "Strokes"); break; case KisPainter::FillStyleGenerator: elt.setAttribute("fillStyle", "Generator"); if (d->generator) { elt.setAttribute("generator", d->generator->name()); QDomElement filterConfigElt = doc.createElement("Generator"); d->generator->toXML(doc, filterConfigElt); elt.appendChild(filterConfigElt); } break; } } KisPaintOpPresetSP KisRecordedPaintAction::paintOpPreset() const { return d->paintOpPreset; } void KisRecordedPaintAction::setPaintOpPreset(KisPaintOpPresetSP preset) { d->paintOpPreset = preset; } qreal KisRecordedPaintAction::opacity() const { return d->opacity; } void KisRecordedPaintAction::setOpacity(qreal opacity) { d->opacity = opacity; } KoColor KisRecordedPaintAction::paintColor() const { return d->foregroundColor; } void KisRecordedPaintAction::setPaintColor(const KoColor& color) { d->foregroundColor = color; } KoColor KisRecordedPaintAction::backgroundColor() const { return d->backgroundColor; } void KisRecordedPaintAction::setBackgroundColor(const KoColor& color) { d->backgroundColor = color; } QString KisRecordedPaintAction::compositeOp() { return d->compositeOp; } void KisRecordedPaintAction::setCompositeOp(const QString& id) { d->compositeOp = id; } void KisRecordedPaintAction::setPaintIncremental(bool v) { d->paintIncremental = v; } void KisRecordedPaintAction::setStrokeStyle(KisPainter::StrokeStyle strokeStyle) { d->strokeStyle = strokeStyle; } void KisRecordedPaintAction::setFillStyle(KisPainter::FillStyle fillStyle) { d->fillStyle = fillStyle; } KisPainter::FillStyle KisRecordedPaintAction::fillStyle() const { return d->fillStyle; } void KisRecordedPaintAction::setPattern(const KoPattern* pattern) { d->pattern = pattern; } void KisRecordedPaintAction::setGradient(const KoAbstractGradient* gradient) { d->gradient = gradient; } void KisRecordedPaintAction::setGenerator(const KisFilterConfigurationSP generator) { d->generator = generator; } void KisRecordedPaintAction::play(KisNodeSP node, const KisPlayInfo& info, KoUpdater* _updater) const { dbgUI << "Play recorded paint action on node : " << node->name() ; KisTransaction transaction(node->paintDevice()); KisPaintDeviceSP target = 0; if (d->paintIncremental) { target = node->paintDevice(); } else { target = node->paintDevice()->createCompositionSourceDevice(); } KisPainter* painter = createPainter(target); painter->setProgress(_updater); if (d->paintIncremental) { painter->setCompositeOp(d->compositeOp); painter->setOpacity(d->opacity * 255); } else { painter->setCompositeOp(node->paintDevice()->colorSpace()->compositeOp(COMPOSITE_ALPHA_DARKEN)); painter->setOpacity(OPACITY_OPAQUE_U8); } painter->setPaintColor(d->foregroundColor); painter->setBackgroundColor(d->backgroundColor); painter->setStrokeStyle(d->strokeStyle); painter->setFillStyle(d->fillStyle); painter->setPattern(d->pattern); painter->setGradient(d->gradient); painter->setGenerator(d->generator); if (d->paintOpPreset) { painter->setPaintOpPreset(d->paintOpPreset, node, info.image()); } playPaint(info, painter); if (!d->paintIncremental) { KisPainter painter2(node->paintDevice()); painter2.setCompositeOp(d->compositeOp); painter2.setOpacity(d->opacity * 255); QVector dirtyRects = painter->takeDirtyRegion(); Q_FOREACH (const QRect &rc, dirtyRects) { painter2.bitBlt(rc.topLeft(), target, rc); } node->setDirty(painter2.takeDirtyRegion()); } else { node->setDirty(painter->takeDirtyRegion()); } delete painter; transaction.commit(info.undoAdapter()); } KisPainter* KisRecordedPaintAction::createPainter(KisPaintDeviceSP device) const { return new KisPainter(device); } void KisRecordedPaintActionFactory::setupPaintAction(KisRecordedPaintAction* action, const QDomElement& elt, const KisRecordedActionLoadContext* context) { QString name = elt.attribute("name"); qreal opacity = opacityFromXML(elt); dbgKrita << ppVar(opacity); bool paintIncremental = paintIncrementalFromXML(elt); QString compositeOp = compositeOpFromXML(elt); // Decode colors KoColor bC = backgroundColorFromXML(elt); KoColor fC = paintColorFromXML(elt); action->setName(name); action->setBackgroundColor(bC); action->setPaintColor(fC); action->setOpacity(opacity); action->setPaintIncremental(paintIncremental); action->setCompositeOp(compositeOp); // Load stroke style QString strokeAttr = elt.attribute("strokeStyle", "None"); if (strokeAttr == "Brush" ) { action->setStrokeStyle(KisPainter::StrokeStyleBrush); } else { // "None" action->setStrokeStyle(KisPainter::StrokeStyleNone); } // Save fill style QString fillAttr = elt.attribute("fillStyle", "None"); if (fillAttr == "PaintColor") { action->setFillStyle(KisPainter::FillStyleForegroundColor); } else if(fillAttr == "AlternativeColor") { action->setFillStyle(KisPainter::FillStyleBackgroundColor); } else if(fillAttr == "Pattern") { const KoPattern* pattern = context->pattern(elt.attribute("pattern")); if (pattern) { action->setFillStyle(KisPainter::FillStylePattern); action->setPattern(pattern); } else { action->setFillStyle(KisPainter::FillStyleNone); } } else if(fillAttr == "Gradient") { const KoAbstractGradient* gradient = context->gradient(elt.attribute("gradient")); if (gradient) { action->setFillStyle(KisPainter::FillStyleGradient); action->setGradient(gradient); } else { action->setFillStyle(KisPainter::FillStyleNone); } } else if(fillAttr == "Strokes") { action->setFillStyle(KisPainter::FillStyleStrokes); } else if(fillAttr == "Generator") { KisGeneratorSP g = KisGeneratorRegistry::instance()->value(elt.attribute("generator")); KisFilterConfigurationSP config = 0; if (g) { config = g->defaultConfiguration(); QDomElement paramsElt = elt.firstChildElement("Generator"); if (config && !paramsElt.isNull()) { config->fromXML(paramsElt); } } if(config) { action->setFillStyle(KisPainter::FillStyleGenerator); action->setGenerator(config); } else { action->setFillStyle(KisPainter::FillStyleNone); } } } KisPaintOpPresetSP KisRecordedPaintActionFactory::paintOpPresetFromXML(const QDomElement& elt) { QDomElement settingsElt = elt.firstChildElement("PaintopPreset"); if (!settingsElt.isNull()) { KisPaintOpPresetSP settings = new KisPaintOpPreset; settings->fromXML(settingsElt); return settings; } else { errImage << "No found"; return 0; } } KoColor KisRecordedPaintActionFactory::paintColorFromXML(const QDomElement& elt) { return colorFromXML(elt, "ForegroundColor"); } KoColor KisRecordedPaintActionFactory::backgroundColorFromXML(const QDomElement& elt) { return colorFromXML(elt, "BackgroundColor"); } KoColor KisRecordedPaintActionFactory::colorFromXML(const QDomElement& elt, const QString& elementName) { QDomElement colorElt = elt.firstChildElement(elementName); KoColor bC; if (!colorElt.isNull()) { - bC = KoColor::fromXML(colorElt.firstChildElement(), Integer8BitsColorDepthID.id(), QHash()); + bC = KoColor::fromXML(colorElt.firstChildElement(), Integer8BitsColorDepthID.id()); bC.setOpacity(quint8(255)); dbgImage << elementName << " color : " << bC.toQColor(); } else { dbgImage << "Warning: no <" << elementName << " /> found"; } return bC; } qreal KisRecordedPaintActionFactory::opacityFromXML(const QDomElement& elt) { return KisDomUtils::toDouble(elt.attribute("opacity", "1.0")); } bool KisRecordedPaintActionFactory::paintIncrementalFromXML(const QDomElement& elt) { return KisDomUtils::toInt(elt.attribute("paintIncremental", "1")); } QString KisRecordedPaintActionFactory::compositeOpFromXML(const QDomElement& elt) { return elt.attribute("compositeOp", COMPOSITE_OVER); } KisNodeQueryPath KisRecordedPaintActionFactory::nodeQueryPathFromXML(const QDomElement& elt) { return KisNodeQueryPath::fromString(elt.attribute("path")); } diff --git a/libs/koplugin/KisMimeDatabase.cpp b/libs/koplugin/KisMimeDatabase.cpp index 637a24c81d..5b12237a8f 100644 --- a/libs/koplugin/KisMimeDatabase.cpp +++ b/libs/koplugin/KisMimeDatabase.cpp @@ -1,267 +1,267 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMimeDatabase.h" #include #include #include #include #include QList KisMimeDatabase::s_mimeDatabase; QString KisMimeDatabase::mimeTypeForFile(const QString &file) { fillMimeData(); QFileInfo fi(file); QString suffix = fi.suffix().toLower(); Q_FOREACH(const KisMimeDatabase::KisMimeType &mimeType, s_mimeDatabase) { if (mimeType.suffixes.contains("*." + suffix)) { debugPlugin << "mimeTypeForFile(). KisMimeDatabase returned" << mimeType.mimeType << "for" << file; return mimeType.mimeType; } } QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(file); if (mime.name() != "application/octet-stream") { debugPlugin << "mimeTypeForFile(). QMimeDatabase returned" << mime.name() << "for" << file; return mime.name(); } return ""; } QString KisMimeDatabase::mimeTypeForSuffix(const QString &suffix) { fillMimeData(); QMimeDatabase db; QString s = suffix.toLower(); if (!s.startsWith("*.")) { s = "*." + s; } Q_FOREACH(const KisMimeDatabase::KisMimeType &mimeType, s_mimeDatabase) { if (mimeType.suffixes.contains(s)) { debugPlugin << "mimeTypeForSuffix(). KisMimeDatabase returned" << mimeType.mimeType << "for" << s; return mimeType.mimeType; } } QMimeType mime = db.mimeTypeForFile(s); if (mime.name() != "application/octet-stream") { debugPlugin << "mimeTypeForSuffix(). QMimeDatabase returned" << mime.name() << "for" << s; return mime.name(); } return ""; } QString KisMimeDatabase::mimeTypeForData(const QByteArray ba) { QMimeDatabase db; QMimeType mtp = db.mimeTypeForData(ba); debugPlugin << "mimeTypeForData(). QMimeDatabase returned" << mtp.name(); return mtp.name(); } QString KisMimeDatabase::descriptionForMimeType(const QString &mimeType) { fillMimeData(); Q_FOREACH(const KisMimeDatabase::KisMimeType &m, s_mimeDatabase) { if (m.mimeType == mimeType) { debugPlugin << "descriptionForMimeType. KisMimeDatabase returned" << m.description << "for" << mimeType; return m.description; } } QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); if (mime.name() != "application/octet-stream") { debugPlugin << "descriptionForMimeType. QMimeDatabase returned" << mime.comment() << "for" << mimeType; return mime.comment(); } return mimeType; } QStringList KisMimeDatabase::suffixesForMimeType(const QString &mimeType) { fillMimeData(); Q_FOREACH(const KisMimeDatabase::KisMimeType &m, s_mimeDatabase) { if (m.mimeType == mimeType) { debugPlugin << "suffixesForMimeType. KisMimeDatabase returned" << m.suffixes; return m.suffixes; } } QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); if (mime.name() != "application/octet-stream" && !mime.suffixes().isEmpty()) { QString preferredSuffix = mime.preferredSuffix(); if (mimeType == "image/x-tga") { preferredSuffix = "tga"; } QStringList suffixes = mime.suffixes(); if (preferredSuffix != suffixes.first()) { suffixes.removeAll(preferredSuffix); suffixes.prepend(preferredSuffix); } debugPlugin << "suffixesForMimeType. QMimeDatabase returned" << suffixes; return suffixes; } return QStringList(); } QString KisMimeDatabase::iconNameForMimeType(const QString &mimeType) { QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); debugPlugin << "iconNameForMimeType" << mime.iconName(); return mime.iconName(); } void KisMimeDatabase::fillMimeData() { // This should come from the import/export plugins, but the json files aren't translated, // which is bad for the description field if (s_mimeDatabase.isEmpty()) { KisMimeType mimeType; mimeType.mimeType = "image/x-gimp-brush"; mimeType.description = i18nc("description of a file type", "Gimp Brush"); mimeType.suffixes = QStringList() << "*.gbr" << "*.vbr"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-gimp-brush-animated"; mimeType.description = i18nc("description of a file type", "Gimp Image Hose Brush"); mimeType.suffixes = QStringList() << "*.gih"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-adobe-brushlibrary"; mimeType.description = i18nc("description of a file type", "Adobe Brush Library"); mimeType.suffixes = QStringList() << "*.abr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-paintoppreset"; mimeType.description = i18nc("description of a file type", "Krita Brush Preset"); mimeType.suffixes = QStringList() << "*.kpp"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-assistant"; mimeType.description = i18nc("description of a file type", "Krita Assistant"); mimeType.suffixes = QStringList() << "*.paintingassistant"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-r16"; mimeType.description = i18nc("description of a file type", "R16 Heightmap"); mimeType.suffixes = QStringList() << "*.r16"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-r8"; mimeType.description = i18nc("description of a file type", "R8 Heightmap"); mimeType.suffixes = QStringList() << "*.r8"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-spriter"; mimeType.description = i18nc("description of a file type", "Spriter SCML"); mimeType.suffixes = QStringList() << "*.scml"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-svm"; mimeType.description = i18nc("description of a file type", "Starview Metafile"); mimeType.suffixes = QStringList() << "*.svm"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/openraster"; mimeType.description = i18nc("description of a file type", "OpenRaster Image"); mimeType.suffixes = QStringList() << "*.ora"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-photoshop-style-library"; mimeType.description = i18nc("description of a file type", "Photoshop Layer Style Library"); mimeType.suffixes = QStringList() << "*.asl"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-color-palette"; mimeType.description = i18nc("description of a file type", "Color Palette"); - mimeType.suffixes = QStringList() << "*.gpl" << "*.pal" << "*.act" << "*.aco" << "*.colors" << "*.xml"; + mimeType.suffixes = QStringList() << "*.gpl" << "*.pal" << "*.act" << "*.aco" << "*.colors" << "*.xml" << "*.sbz"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-opencolorio-configuration"; mimeType.description = i18nc("description of a file type", "OpenColorIO Configuration"); mimeType.suffixes = QStringList() << "*.ocio"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-recorded-macro"; mimeType.description = i18nc("description of a file type", "Krita Recorded Action"); mimeType.suffixes = QStringList() << "*.krarec"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-gradient"; mimeType.description = i18nc("description of a file type", "GIMP Gradients"); mimeType.suffixes = QStringList() << "*.ggr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-pattern"; mimeType.description = i18nc("description of a file type", "GIMP Patterns"); mimeType.suffixes = QStringList() << "*.pat"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-karbon-gradient"; mimeType.description = i18nc("description of a file type", "Karbon Gradients"); mimeType.suffixes = QStringList() << "*.kgr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-bundle"; mimeType.description = i18nc("description of a file type", "Krita Resource Bundle"); mimeType.suffixes = QStringList() << "*.bundle"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-workspace"; mimeType.description = i18nc("description of a file type", "Krita Workspace"); mimeType.suffixes = QStringList() << "*.kws"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-taskset"; mimeType.description = i18nc("description of a file type", "Krita Taskset"); mimeType.suffixes = QStringList() << "*.kts"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-krita-raw"; mimeType.description = i18nc("description of a file type", "Camera Raw Files"); mimeType.suffixes = QStringList() << "*.nef" << "*.cr2" << "*.sr2" << "*.crw" << "*.pef" << "*.x3f" << "*.kdc" << "*.mrw" << "*.arw" << "*.k25" << "*.dcr" << "*.orf" << "*.raw" << "*.raw" << "*.raf" << "*.srf" << "*.dng"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-extension-exr"; mimeType.description = i18nc("description of a file type", "OpenEXR (Extended)"); mimeType.suffixes = QStringList() << "*.exr"; s_mimeDatabase << mimeType; debugPlugin << "Filled mimedatabase with" << s_mimeDatabase.count() << "special mimetypes"; } } diff --git a/libs/pigment/CMakeLists.txt b/libs/pigment/CMakeLists.txt index 3c67d26329..0920b9e0ba 100644 --- a/libs/pigment/CMakeLists.txt +++ b/libs/pigment/CMakeLists.txt @@ -1,131 +1,132 @@ project(kritapigment) # we have to repeat platform specifics from top-level if (WIN32) include_directories(${CMAKE_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif () include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_SOURCE_DIR}/compositeops) include_directories(SYSTEM ${Boost_INCLUDE_DIRS} ) set(FILE_OPENEXR_SOURCES) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() set(LINK_VC_LIB) if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR}) set(LINK_VC_LIB ${Vc_LIBRARIES}) ko_compile_for_all_implementations_no_scalar(__per_arch_factory_objs compositeops/KoOptimizedCompositeOpFactoryPerArch.cpp) message("Following objects are generated from the per-arch lib") message(${__per_arch_factory_objs}) endif() add_subdirectory(tests) add_subdirectory(benchmarks) set(kritapigment_SRCS DebugPigment.cpp KoBasicHistogramProducers.cpp KoColor.cpp KoColorDisplayRendererInterface.cpp KoColorConversionAlphaTransformation.cpp KoColorConversionCache.cpp KoColorConversions.cpp KoColorConversionSystem.cpp KoColorConversionTransformation.cpp KoColorProofingConversionTransformation.cpp KoColorConversionTransformationFactory.cpp KoColorModelStandardIds.cpp KoColorProfile.cpp KoColorSpace.cpp KoColorSpaceEngine.cpp KoColorSpaceFactory.cpp KoColorSpaceMaths.cpp KoColorSpaceRegistry.cpp KoColorTransformation.cpp KoColorTransformationFactory.cpp KoColorTransformationFactoryRegistry.cpp KoCompositeColorTransformation.cpp KoCompositeOp.cpp KoCompositeOpRegistry.cpp KoCopyColorConversionTransformation.cpp KoFallBackColorTransformation.cpp KoHistogramProducer.cpp KoMultipleColorConversionTransformation.cpp KoUniqueNumberForIdServer.cpp colorspaces/KoAlphaColorSpace.cpp colorspaces/KoAlphaU16ColorSpace.cpp colorspaces/KoAlphaF16ColorSpace.cpp colorspaces/KoAlphaF32ColorSpace.cpp colorspaces/KoLabColorSpace.cpp colorspaces/KoRgbU16ColorSpace.cpp colorspaces/KoRgbU8ColorSpace.cpp colorspaces/KoSimpleColorSpaceEngine.cpp compositeops/KoOptimizedCompositeOpFactory.cpp compositeops/KoOptimizedCompositeOpFactoryPerArch_Scalar.cpp ${__per_arch_factory_objs} colorprofiles/KoDummyColorProfile.cpp resources/KoAbstractGradient.cpp resources/KoColorSet.cpp resources/KoPattern.cpp resources/KoResource.cpp resources/KoMD5Generator.cpp resources/KoHashGeneratorProvider.cpp resources/KoStopGradient.cpp resources/KoSegmentGradient.cpp ) if (HAVE_LCMS24 AND OPENEXR_FOUND) set (kritapigment_SRCS ${kritapigment_SRCS} colorspaces/KoAlphaF16ColorSpace.cpp ) endif() set (EXTRA_LIBRARIES ${LINK_OPENEXR_LIB} ${LINK_VC_LIB}) if(MSVC OR (WIN32 AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")) # avoid "cannot open file 'LIBC.lib'" error set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /NODEFAULTLIB:LIBC.LIB") endif() add_library(kritapigment SHARED ${kritapigment_SRCS}) generate_export_header(kritapigment) target_include_directories( kritapigment PUBLIC $ $ ) target_link_libraries( kritapigment PUBLIC kritaplugin + kritastore ${EXTRA_LIBRARIES} KF5::I18n KF5::ConfigCore Qt5::Core Qt5::Gui Qt5::Xml ${WIN32_PLATFORM_NET_LIBS} ) set_target_properties(kritapigment PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritapigment ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/pigment/KoColor.cpp b/libs/pigment/KoColor.cpp index 62826bdc0e..e29daf6f05 100644 --- a/libs/pigment/KoColor.cpp +++ b/libs/pigment/KoColor.cpp @@ -1,340 +1,336 @@ /* * Copyright (c) 2005 Boudewijn Rempt * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this 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 "KoColor.h" #include #include #include "DebugPigment.h" #include "KoColorModelStandardIds.h" #include "KoColorProfile.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "KoChannelInfo.h" class Q_DECL_HIDDEN KoColor::Private { public: Private() : data(0), colorSpace(0) {} ~Private() { delete [] data; } quint8 * data; const KoColorSpace * colorSpace; }; KoColor::KoColor() : d(new Private()) { d->colorSpace = KoColorSpaceRegistry::instance()->rgb16(0); d->data = new quint8[d->colorSpace->pixelSize()]; d->colorSpace->fromQColor(Qt::black, d->data); d->colorSpace->setOpacity(d->data, OPACITY_OPAQUE_U8, 1); } KoColor::KoColor(const KoColorSpace * colorSpace) : d(new Private()) { Q_ASSERT(colorSpace); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); d->data = new quint8[d->colorSpace->pixelSize()]; memset(d->data, 0, d->colorSpace->pixelSize()); } KoColor::~KoColor() { delete d; } KoColor::KoColor(const QColor & color, const KoColorSpace * colorSpace) : d(new Private()) { Q_ASSERT(color.isValid()); Q_ASSERT(colorSpace); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); d->data = new quint8[colorSpace->pixelSize()]; memset(d->data, 0, d->colorSpace->pixelSize()); d->colorSpace->fromQColor(color, d->data); } KoColor::KoColor(const quint8 * data, const KoColorSpace * colorSpace) : d(new Private()) { Q_ASSERT(colorSpace); Q_ASSERT(data); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); d->data = new quint8[colorSpace->pixelSize()]; memset(d->data, 0, d->colorSpace->pixelSize()); memmove(d->data, data, colorSpace->pixelSize()); } KoColor::KoColor(const KoColor &src, const KoColorSpace * colorSpace) : d(new Private()) { Q_ASSERT(colorSpace); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); d->data = new quint8[colorSpace->pixelSize()]; memset(d->data, 0, d->colorSpace->pixelSize()); src.colorSpace()->convertPixelsTo(src.d->data, d->data, colorSpace, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } KoColor::KoColor(const KoColor & rhs) : d(new Private()) { d->colorSpace = rhs.colorSpace(); Q_ASSERT(*d->colorSpace == *KoColorSpaceRegistry::instance()->permanentColorspace(d->colorSpace)); if (d->colorSpace && rhs.d->data) { d->data = new quint8[d->colorSpace->pixelSize()]; memcpy(d->data, rhs.d->data, d->colorSpace->pixelSize()); } } KoColor & KoColor::operator=(const KoColor & rhs) { if (this == &rhs) return *this; delete [] d->data; d->data = 0; d->colorSpace = rhs.colorSpace(); if (rhs.d->colorSpace && rhs.d->data) { Q_ASSERT(d->colorSpace == KoColorSpaceRegistry::instance()->permanentColorspace(d->colorSpace)); // here we want to do a check on pointer, since d->colorSpace is supposed to already be a permanent one d->data = new quint8[d->colorSpace->pixelSize()]; memcpy(d->data, rhs.d->data, d->colorSpace->pixelSize()); } return * this; } bool KoColor::operator==(const KoColor &other) const { if (*colorSpace() != *other.colorSpace()) return false; return memcmp(d->data, other.d->data, d->colorSpace->pixelSize()) == 0; } void KoColor::convertTo(const KoColorSpace * cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { //dbgPigment <<"Our colormodel:" << d->colorSpace->id().name() // << ", new colormodel: " << cs->id().name() << "\n"; if (*d->colorSpace == *cs) return; quint8 * data = new quint8[cs->pixelSize()]; memset(data, 0, cs->pixelSize()); d->colorSpace->convertPixelsTo(d->data, data, cs, 1, renderingIntent, conversionFlags); delete [] d->data; d->data = data; d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(cs); } void KoColor::convertTo(const KoColorSpace * cs) { convertTo(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } void KoColor::setProfile(const KoColorProfile *profile) { const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return; d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(dstColorSpace); } void KoColor::setColor(const quint8 * data, const KoColorSpace * colorSpace) { Q_ASSERT(data); Q_ASSERT(colorSpace); if(d->colorSpace->pixelSize() != colorSpace->pixelSize()) { delete [] d->data; d->data = new quint8[colorSpace->pixelSize()]; } memcpy(d->data, data, colorSpace->pixelSize()); d->colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace); } // To save the user the trouble of doing color->colorSpace()->toQColor(color->data(), &c, &a, profile void KoColor::toQColor(QColor *c) const { Q_ASSERT(c); if (d->colorSpace && d->data) { d->colorSpace->toQColor(d->data, c); } } QColor KoColor::toQColor() const { QColor c; toQColor(&c); return c; } void KoColor::fromQColor(const QColor& c) const { if (d->colorSpace && d->data) { d->colorSpace->fromQColor(c, d->data); } } #ifndef NDEBUG void KoColor::dump() const { dbgPigment <<"KoColor (" << this <<")," << d->colorSpace->id() <<""; QList channels = d->colorSpace->channels(); QList::const_iterator begin = channels.constBegin(); QList::const_iterator end = channels.constEnd(); for (QList::const_iterator it = begin; it != end; ++it) { KoChannelInfo * ch = (*it); // XXX: setNum always takes a byte. if (ch->size() == sizeof(quint8)) { // Byte dbgPigment <<"Channel (byte):" << ch->name() <<":" << QString().setNum(d->data[ch->pos()]) <<""; } else if (ch->size() == sizeof(quint16)) { // Short (may also by an nvidia half) dbgPigment <<"Channel (short):" << ch->name() <<":" << QString().setNum(*((const quint16 *)(d->data+ch->pos()))) <<""; } else if (ch->size() == sizeof(quint32)) { // Integer (may also be float... Find out how to distinguish these!) dbgPigment <<"Channel (int):" << ch->name() <<":" << QString().setNum(*((const quint32 *)(d->data+ch->pos()))) <<""; } } } #endif void KoColor::fromKoColor(const KoColor& src) { src.colorSpace()->convertPixelsTo(src.d->data, d->data, colorSpace(), 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } const KoColorProfile * KoColor::profile() const { return d->colorSpace->profile(); } quint8 * KoColor::data() { return d->data; } const quint8 * KoColor::data() const { return d->data; } const KoColorSpace * KoColor::colorSpace() const { return d->colorSpace; } void KoColor::toXML(QDomDocument& doc, QDomElement& colorElt) const { d->colorSpace->colorToXML(d->data, doc, colorElt); } void KoColor::setOpacity(quint8 alpha) { d->colorSpace->setOpacity(d->data, alpha, 1); } void KoColor::setOpacity(qreal alpha) { d->colorSpace->setOpacity(d->data, alpha, 1); } quint8 KoColor::opacityU8() const { return d->colorSpace->opacityU8(d->data); } qreal KoColor::opacityF() const { return d->colorSpace->opacityF(d->data); } -KoColor KoColor::fromXML(const QDomElement& elt, const QString & bitDepthId, const QHash & aliases) +KoColor KoColor::fromXML(const QDomElement& elt, const QString & bitDepthId) { QString modelId; if (elt.tagName() == "CMYK") { modelId = CMYKAColorModelID.id(); } else if (elt.tagName() == "RGB") { modelId = RGBAColorModelID.id(); } else if (elt.tagName() == "sRGB") { modelId = RGBAColorModelID.id(); } else if (elt.tagName() == "Lab") { modelId = LABAColorModelID.id(); } else if (elt.tagName() == "XYZ") { modelId = XYZAColorModelID.id(); } else if (elt.tagName() == "Gray") { modelId = GrayAColorModelID.id(); } else if (elt.tagName() == "YCbCr") { modelId = YCbCrAColorModelID.id(); } QString profileName; if (elt.tagName() != "sRGB") { profileName = elt.attribute("space", ""); - const QHash::ConstIterator it = aliases.find(profileName); - if (it != aliases.end()) { - profileName = it.value(); - } if (!KoColorSpaceRegistry::instance()->profileByName(profileName)) { profileName.clear(); } } const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, bitDepthId, profileName); if (cs == 0) { QList list = KoColorSpaceRegistry::instance()->colorDepthList(modelId, KoColorSpaceRegistry::AllColorSpaces); if (!list.empty()) { cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, list[0].id(), profileName); } } if (cs) { KoColor c(cs); cs->colorFromXML(c.data(), elt); return c; } else { return KoColor(); } } QString KoColor::toQString(const KoColor &color) { QStringList ls; Q_FOREACH (KoChannelInfo *channel, KoChannelInfo::displayOrderSorted(color.colorSpace()->channels())) { int realIndex = KoChannelInfo::displayPositionToChannelIndex(channel->displayPosition(), color.colorSpace()->channels()); ls << channel->name(); ls << color.colorSpace()->channelValueText(color.data(), realIndex); } return ls.join(" "); } diff --git a/libs/pigment/KoColor.h b/libs/pigment/KoColor.h index 553f3a0d53..b4653f25f7 100644 --- a/libs/pigment/KoColor.h +++ b/libs/pigment/KoColor.h @@ -1,176 +1,173 @@ /* * Copyright (c) 2005 Boudewijn Rempt * Copyright (C) 2007 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLOR_H #define KOCOLOR_H #include #include #include "kritapigment_export.h" #include "KoColorConversionTransformation.h" class QDomDocument; class QDomElement; class KoColorProfile; class KoColorSpace; /** * A KoColor describes a color in a certain colorspace. The color is stored in a buffer * that can be manipulated by the function of the color space. */ class KRITAPIGMENT_EXPORT KoColor { public: /// Create an empty KoColor. It will be valid, but also black and transparent KoColor(); ~KoColor(); /// Create a null KoColor. It will be valid, but all channels will be set to 0 explicit KoColor(const KoColorSpace * colorSpace); /// Create a KoColor from a QColor. The QColor is immediately converted to native. The QColor /// is assumed to have the current monitor profile. KoColor(const QColor & color, const KoColorSpace * colorSpace); /// Create a KoColor using a native color strategy. The data is copied. KoColor(const quint8 * data, const KoColorSpace * colorSpace); /// Create a KoColor by converting src into another colorspace KoColor(const KoColor &src, const KoColorSpace * colorSpace); /// Copy constructor -- deep copies the colors. KoColor(const KoColor & rhs); /** * assignment operator to copy the data from the param color into this one. * @param other the color we are going to copy * @return this color */ KoColor &operator=(const KoColor &other); bool operator==(const KoColor &other) const; /// return the current colorSpace const KoColorSpace * colorSpace() const; /// return the current profile const KoColorProfile * profile() const; /// Convert this KoColor to the specified colorspace. If the specified colorspace is the /// same as the original colorspace, do nothing. Returns the converted KoColor. void convertTo(const KoColorSpace * cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); void convertTo(const KoColorSpace * cs); /// assign new profile without converting pixel data void setProfile(const KoColorProfile *profile); /// Replace the existing color data, and colorspace with the specified data. /// The data is copied. void setColor(const quint8 * data, const KoColorSpace * colorSpace = 0); /// Convert the color from src and replace the value of the current color with the converted data. /// Don't convert the color if src and this have the same colorspace. void fromKoColor(const KoColor& src); /// a convenience method for the above. void toQColor(QColor *c) const; /// a convenience method for the above. QColor toQColor() const; /** * Convenient function to set the opacity of the color. */ void setOpacity(quint8 alpha); void setOpacity(qreal alpha); /** * Convenient function that return the opacity of the color */ quint8 opacityU8() const; qreal opacityF() const; -// what about making the next two methods static factory methods? /// Convenient function for converting from a QColor void fromQColor(const QColor& c) const; /** * @return the buffer associated with this color object to be used with the * transformation object created by the color space of this KoColor * or to copy to a different buffer from the same color space */ quint8 * data(); /** * @return the buffer associated with this color object to be used with the * transformation object created by the color space of this KoColor * or to copy to a different buffer from the same color space */ const quint8 * data() const; /** * Serialize this color following Create's swatch color specification available * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format * * This function doesn't create the element but rather the , * , ... elements. It is assumed that colorElt is the * element. * * @param colorElt root element for the serialization, it is assumed that this * element is * @param doc is the document containing colorElt */ void toXML(QDomDocument& doc, QDomElement& colorElt) const; /** * Unserialize a color following Create's swatch color specification available * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format * * @param elt the element to unserialize (, , ) * @param bitDepthId the bit depth is unspecified by the spec, this allow to select * a preferred bit depth for creating the KoColor object (if that * bit depth isn't available, this function will randomly select * an other bit depth) - * @param profileAliases alias between the profile name specified by the "space" - * attribute and the profile name used inside pigment * @return the unserialize color, or an empty color object if the function failed * to unserialize the color */ - static KoColor fromXML(const QDomElement& elt, const QString & bitDepthId, const QHash & aliases); + static KoColor fromXML(const QDomElement& elt, const QString & bitDepthId); static QString toQString(const KoColor &color); #ifndef NODEBUG /// use qDebug calls to print internal info void dump() const; #endif private: class Private; Private * const d; }; Q_DECLARE_METATYPE(KoColor) #endif diff --git a/libs/pigment/resources/KoColorSet.cpp b/libs/pigment/resources/KoColorSet.cpp index 5512eaf80f..732b86e359 100644 --- a/libs/pigment/resources/KoColorSet.cpp +++ b/libs/pigment/resources/KoColorSet.cpp @@ -1,742 +1,1512 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt + Copyright (c) 2016 L. E. Segovia This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include // qFromLittleEndian #include #include #include #include #include #include #include #include #include +#include +#include +#include #include #include +#include +#include +#include #include #include +#include #include "KoColor.h" +#include "KoColorProfile.h" #include "KoColorSpaceRegistry.h" #include "KoColorModelStandardIds.h" struct KoColorSet::Private { + KoColorSet::PaletteType paletteType; QByteArray data; - QString name; QString comment; qint32 columns; - QVector colors; + QVector colors; //ungrouped colors + QStringList groupNames; //names of the groups, this is used to determine the order they are in. + QMap> groups; //grouped colors. }; KoColorSet::PaletteType detectFormat(const QString &fileName, const QByteArray &ba) { QFileInfo fi(fileName); // .pal if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) { return KoColorSet::RIFF_PAL; } // .gpl else if (ba.startsWith("GIMP Palette")) { return KoColorSet::GPL; } // .pal else if (ba.startsWith("JASC-PAL")) { return KoColorSet::PSP_PAL; } else if (fi.suffix().toLower() == "aco") { return KoColorSet::ACO; } else if (fi.suffix().toLower() == "act") { return KoColorSet::ACT; } else if (fi.suffix().toLower() == "xml") { return KoColorSet::XML; } - + else if (fi.suffix().toLower() == "kpl") { + return KoColorSet::KPL; + } + else if (fi.suffix().toLower() == "sbz") { + return KoColorSet::SBZ; + } return KoColorSet::UNKNOWN; } KoColorSet::KoColorSet(const QString& filename) : KoResource(filename) , d(new Private()) { // Implemented in KoResource class d->columns = 0; // Set the default value that the GIMP uses... } KoColorSet::KoColorSet() : KoResource(QString()) , d(new Private()) { d->columns = 0; // Set the default value that the GIMP uses... } /// Create an copied palette KoColorSet::KoColorSet(const KoColorSet& rhs) : QObject(0) , KoResource(QString()) , d(new Private()) { setFilename(rhs.filename()); - d->name = rhs.d->name; d->comment = rhs.d->comment; d->columns = rhs.d->columns; d->colors = rhs.d->colors; + d->groupNames = rhs.d->groupNames; + d->groups = rhs.d->groups; setValid(true); } KoColorSet::~KoColorSet() { } bool KoColorSet::load() { QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { warnPigment << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KoColorSet::loadFromDevice(QIODevice *dev) { if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); d->data = dev->readAll(); Q_ASSERT(d->data.size() != 0); return init(); } bool KoColorSet::save() { QFile file(filename()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } saveToDevice(&file); file.close(); return true; } -qint32 KoColorSet::nColors() -{ - return d->colors.count(); -} - -qint32 KoColorSet::getIndexClosestColor(KoColor color, bool useGivenColorSpace) -{ - qint32 closestIndex = 0; - quint8 highestPercentage = 0; - quint8 testPercentage = 0; - KoColor compare = color; - for (qint32 i=0; icolors.at(i).color; - if (useGivenColorSpace==true && compare.colorSpace()!=entry.colorSpace()) { - entry.convertTo(compare.colorSpace()); - - } else if(compare.colorSpace()!=entry.colorSpace()) { - compare.convertTo(entry.colorSpace()); - } - testPercentage = (255 - compare.colorSpace()->difference(compare.data(), entry.data())); - if (testPercentage>highestPercentage) - { - closestIndex = i; - highestPercentage = testPercentage; - } - } - return closestIndex; -} - -QString KoColorSet::closestColorName(KoColor color, bool useGivenColorSpace) -{ - int i = getIndexClosestColor(color, useGivenColorSpace); - QString name = d->colors.at(i).name; - return name; -} - bool KoColorSet::saveToDevice(QIODevice *dev) const { - QTextStream stream(dev); - stream << "GIMP Palette\nName: " << name() << "\nColumns: " << d->columns << "\n#\n"; - - for (int i = 0; i < d->colors.size(); i++) { - const KoColorSetEntry& entry = d->colors.at(i); - QColor c = entry.color.toQColor(); - stream << c.red() << " " << c.green() << " " << c.blue() << "\t"; - if (entry.name.isEmpty()) - stream << "Untitled\n"; - else - stream << entry.name << "\n"; + bool res; + switch(d->paletteType) { + case GPL: + res = saveGpl(dev); + break; + default: + res = saveKpl(dev); } - - KoResource::saveToDevice(dev); - - return true; + if (res) { + KoResource::saveToDevice(dev); + } + return res; } bool KoColorSet::init() { d->colors.clear(); // just in case this is a reload (eg by KoEditColorSetDialog), + d->groups.clear(); + d->groupNames.clear(); if (filename().isNull()) { warnPigment << "Cannot load palette" << name() << "there is no filename set"; return false; } if (d->data.isNull()) { QFile file(filename()); if (file.size() == 0) { warnPigment << "Cannot load palette" << name() << "there is no data available"; return false; } file.open(QIODevice::ReadOnly); d->data = file.readAll(); file.close(); } bool res = false; - PaletteType paletteType = detectFormat(filename(), d->data); - switch(paletteType) { + d->paletteType = detectFormat(filename(), d->data); + switch(d->paletteType) { case GPL: res = loadGpl(); break; case ACT: res = loadAct(); break; case RIFF_PAL: res = loadRiff(); break; case PSP_PAL: res = loadPsp(); break; case ACO: res = loadAco(); break; case XML: res = loadXml(); break; + case KPL: + res = loadKpl(); + break; + case SBZ: + res = loadSbz(); + break; default: res = false; } setValid(res); if (d->columns == 0) { d->columns = 10; } QImage img(d->columns * 4, (d->colors.size() / d->columns) * 4, QImage::Format_ARGB32); QPainter gc(&img); gc.fillRect(img.rect(), Qt::darkGray); int counter = 0; for(int i = 0; i < d->columns; ++i) { for (int j = 0; j < (d->colors.size() / d->columns); ++j) { if (counter < d->colors.size()) { QColor c = d->colors.at(counter).color.toQColor(); gc.fillRect(i * 4, j * 4, 4, 4, c); counter++; } else { break; } } } setImage(img); // save some memory d->data.clear(); return res; } -void KoColorSet::add(const KoColorSetEntry & c) +bool KoColorSet::saveGpl(QIODevice *dev) const { - d->colors.push_back(c); + QTextStream stream(dev); + stream << "GIMP Palette\nName: " << name() << "\nColumns: " << d->columns << "\n#\n"; + + for (int i = 0; i < d->colors.size(); i++) { + const KoColorSetEntry& entry = d->colors.at(i); + QColor c = entry.color.toQColor(); + stream << c.red() << " " << c.green() << " " << c.blue() << "\t"; + if (entry.name.isEmpty()) + stream << "Untitled\n"; + else + stream << entry.name << "\n"; + } + + return true; } -void KoColorSet::remove(const KoColorSetEntry & c) +quint32 KoColorSet::nColors() { - for (auto it = d->colors.begin(); it != d->colors.end(); /*noop*/) { - if ((*it) == c) { - it = d->colors.erase(it); - return; + quint32 total = d->colors.count(); + if (!d->groups.empty()) { + Q_FOREACH (const QVector &group, d->groups.values()) { + total += group.size(); } - ++it; } + return total; } -void KoColorSet::removeAt(quint32 index) +quint32 KoColorSet::nColorsGroup(QString groupName) { + if (d->groups.contains(groupName)) { + return d->groups.value(groupName).size(); + } else if (groupName.isEmpty()){ + return d->colors.size(); + } else { + return 0; + } +} + +quint32 KoColorSet::getIndexClosestColor(const KoColor color, bool useGivenColorSpace) +{ + quint32 closestIndex = 0; + quint8 highestPercentage = 0; + quint8 testPercentage = 0; + KoColor compare = color; + for (quint32 i=0; idifference(compare.data(), entry.data())); + if (testPercentage>highestPercentage) + { + closestIndex = i; + highestPercentage = testPercentage; + } + } + return closestIndex; +} + +QString KoColorSet::closestColorName(const KoColor color, bool useGivenColorSpace) +{ + int i = getIndexClosestColor(color, useGivenColorSpace); + QString name = d->colors.at(i).name; + return name; +} + +void KoColorSet::add(const KoColorSetEntry & c, QString groupName) +{ + if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) { + d->groups[groupName].push_back(c); + } else { + d->colors.push_back(c); + } +} + +quint32 KoColorSet::insertBefore(const KoColorSetEntry &c, qint32 index, const QString &groupName) +{ + quint32 newIndex = index; + if (d->groups.contains(groupName)) { + d->groups[groupName].insert(index, c); + } else if (groupName.isEmpty()){ + d->colors.insert(index, c);; + } else { + warnPigment << "Couldn't find group to insert to"; + } + return newIndex; +} + +void KoColorSet::removeAt(quint32 index, QString groupName) { - d->colors.remove(index); + if (d->groups.contains(groupName)){ + if ((quint32)d->groups.value(groupName).size()>index) { + d->groups[groupName].remove(index); + } + } else { + if ((quint32)d->colors.size()>index) { + d->colors.remove(index); + } + } } void KoColorSet::clear() { d->colors.clear(); + d->groups.clear(); } -KoColorSetEntry KoColorSet::getColor(quint32 index) +KoColorSetEntry KoColorSet::getColorGlobal(quint32 index) { - return d->colors[index]; + KoColorSetEntry e; + quint32 groupIndex = index; + QString groupName = findGroupByGlobalIndex(index, &groupIndex); + e = getColorGroup(groupIndex, groupName); + return e; +} + +KoColorSetEntry KoColorSet::getColorGroup(quint32 index, QString groupName) +{ + KoColorSetEntry e; + if (d->groups.contains(groupName) && index<(quint32)d->groups.value(groupName).size()) { + e = d->groups.value(groupName).at(index); + } else if (groupName == QString() && index<(quint32)d->colors.size()) { + e = d->colors.at(index); + } else { + warnPigment << "Color group "<colors.size()<=*index) { + *index -= (quint32)d->colors.size(); + if (!d->groups.empty() || !d->groupNames.empty()) { + QStringList groupNames = getGroupNames(); + Q_FOREACH (QString name, groupNames) { + quint32 size = (quint32)d->groups.value(name).size(); + if (size<=*index) { + *index -= size; + } else { + groupName = name; + return groupName; + } + } + + } + } + return groupName; +} + +QString KoColorSet::findGroupByColorName(const QString &name, quint32 *index) +{ + *index = 0; + QString groupName = QString(); + for (int i = 0; icolors.size(); i++) { + if(d->colors.at(i).name == name) { + *index = (quint32)i; + return groupName; + } + } + QStringList groupNames = getGroupNames(); + Q_FOREACH (QString name, groupNames) { + for (int i=0; igroups[name].size(); i++) { + if(d->groups[name].at(i).name == name) { + *index = (quint32)i; + groupName = name; + return groupName; + } + } + } + return groupName; +} + +QString KoColorSet::findGroupByID(const QString &id, quint32 *index) { + *index = 0; + QString groupName = QString(); + for (int i = 0; icolors.size(); i++) { + if(d->colors.at(i).id == id) { + *index = (quint32)i; + return groupName; + } + } + QStringList groupNames = getGroupNames(); + Q_FOREACH (QString name, groupNames) { + for (int i=0; igroups[name].size(); i++) { + if(d->groups[name].at(i).id == id) { + *index = (quint32)i; + groupName = name; + return groupName; + } + } + } + return groupName; +} + +QStringList KoColorSet::getGroupNames() +{ + if (d->groupNames.size()groups.size()) { + warnPigment << "mismatch between groups and the groupnames list."; + return QStringList(d->groups.keys()); + } + return d->groupNames; } void KoColorSet::setColumnCount(int columns) { d->columns = columns; } int KoColorSet::columnCount() { return d->columns; } +QString KoColorSet::comment() +{ + return d->comment; +} + +bool KoColorSet::addGroup(const QString &groupName) +{ + if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) { + return false; + } + d->groupNames.append(groupName); + d->groups[groupName]; + return true; +} + +bool KoColorSet::removeGroup(const QString &groupName, bool keepColors) +{ + if (!d->groups.contains(groupName)) { + return false; + } + if (keepColors) { + for (int i = 0; igroups.value(groupName).size(); i++) { + d->colors.append(d->groups.value(groupName).at(i)); + } + } + for(int n = 0; ngroupNames.size(); n++) { + if (d->groupNames.at(n) == groupName) { + d->groupNames.removeAt(n); + } + } + + d->groups.remove(groupName); + return true; +} + QString KoColorSet::defaultFileExtension() const { - return QString(".gpl"); + return QString(".kpl"); } bool KoColorSet::loadGpl() { QString s = QString::fromUtf8(d->data.data(), d->data.count()); if (s.isEmpty() || s.isNull() || s.length() < 50) { warnPigment << "Illegal Gimp palette file: " << filename(); return false; } quint32 index = 0; QStringList lines = s.split('\n', QString::SkipEmptyParts); if (lines.size() < 3) { return false; } QString columns; qint32 r, g, b; KoColorSetEntry e; // Read name if (!lines[0].startsWith("GIMP") || !lines[1].startsWith("Name: ")) { warnPigment << "Illegal Gimp palette file: " << filename(); return false; } setName(i18n(lines[1].mid(strlen("Name: ")).trimmed().toLatin1())); index = 2; // Read columns if (lines[index].startsWith("Columns: ")) { columns = lines[index].mid(strlen("Columns: ")).trimmed(); d->columns = columns.toInt(); index = 3; } for (qint32 i = index; i < lines.size(); i++) { if (lines[i].startsWith('#')) { d->comment += lines[i].mid(1).trimmed() + ' '; } else if (!lines[i].isEmpty()) { QStringList a = lines[i].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() < 3) { break; } r = a[0].toInt(); a.pop_front(); g = a[0].toInt(); a.pop_front(); b = a[0].toInt(); a.pop_front(); r = qBound(0, r, 255); g = qBound(0, g, 255); b = qBound(0, b, 255); e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); QString name = a.join(" "); e.name = name.isEmpty() ? i18n("Untitled") : name; add(e); } } return true; } bool KoColorSet::loadAct() { QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; for (int i = 0; i < d->data.size(); i += 3) { quint8 r = d->data[i]; quint8 g = d->data[i+1]; quint8 b = d->data[i+2]; e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); add(e); } return true; } struct RiffHeader { quint32 riff; quint32 size; quint32 signature; quint32 data; quint32 datasize; quint16 version; quint16 colorcount; }; bool KoColorSet::loadRiff() { // http://worms2d.info/Palette_file QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; RiffHeader header; memcpy(&header, d->data.constData(), sizeof(RiffHeader)); header.colorcount = qFromBigEndian(header.colorcount); for (int i = sizeof(RiffHeader); (i < (int)(sizeof(RiffHeader) + header.colorcount) && i < d->data.size()); i += 4) { quint8 r = d->data[i]; quint8 g = d->data[i+1]; quint8 b = d->data[i+2]; e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); add(e); } return true; } bool KoColorSet::loadPsp() { QFileInfo info(filename()); setName(info.baseName()); KoColorSetEntry e; qint32 r, g, b; QString s = QString::fromUtf8(d->data.data(), d->data.count()); QStringList l = s.split('\n', QString::SkipEmptyParts); if (l.size() < 4) return false; if (l[0] != "JASC-PAL") return false; if (l[1] != "0100") return false; int entries = l[2].toInt(); for (int i = 0; i < entries; ++i) { QStringList a = l[i + 3].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() != 3) { continue; } r = a[0].toInt(); a.pop_front(); g = a[0].toInt(); a.pop_front(); b = a[0].toInt(); a.pop_front(); r = qBound(0, r, 255); g = qBound(0, g, 255); b = qBound(0, b, 255); e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); e.color.fromQColor(QColor(r, g, b)); QString name = a.join(" "); e.name = name.isEmpty() ? i18n("Untitled") : name; add(e); } return true; } void scribusParseColor(KoColorSet *set, QXmlStreamReader *xml) { KoColorSetEntry currentColor; //It's a color, retrieve it QXmlStreamAttributes colorProperties = xml->attributes(); QStringRef colorValue; // RGB or CMYK? if (colorProperties.hasAttribute("RGB")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", RGB " << colorProperties.value("RGB"); QStringRef colorName = colorProperties.value("NAME"); currentColor.name = colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString(); currentColor.color = KoColor(KoColorSpaceRegistry::instance()->rgb8()); colorValue = colorProperties.value("RGB"); if (colorValue.length() != 7 && colorValue.at(0) != '#') { // Color is a hexadecimal number xml->raiseError("Invalid rgb8 color (malformed): " + colorValue); return; } else { bool rgbOk; quint32 rgb = colorValue.mid(1).toUInt(&rgbOk, 16); if (!rgbOk) { xml->raiseError("Invalid rgb8 color (unable to convert): " + colorValue); return; } quint8 r = rgb >> 16 & 0xff; quint8 g = rgb >> 8 & 0xff; quint8 b = rgb & 0xff; dbgPigment << "Color parsed: "<< r << g << b; currentColor.color.data()[0] = r; currentColor.color.data()[1] = g; currentColor.color.data()[2] = b; currentColor.color.setOpacity(OPACITY_OPAQUE_U8); set->add(currentColor); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else if (colorProperties.hasAttribute("CMYK")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", CMYK " << colorProperties.value("CMYK"); QStringRef colorName = colorProperties.value("NAME"); currentColor.name = colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString(); currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString())); colorValue = colorProperties.value("CMYK"); if (colorValue.length() != 9 && colorValue.at(0) != '#') { // Color is a hexadecimal number - xml->raiseError("Invalid cmyk color (malformed): " + colorValue); + xml->raiseError("Invalid cmyk color (malformed): " % colorValue); return; } else { bool cmykOk; quint32 cmyk = colorValue.mid(1).toUInt(&cmykOk, 16); // cmyk uses the full 32 bits if (!cmykOk) { - xml->raiseError("Invalid cmyk color (unable to convert): " + colorValue); + xml->raiseError("Invalid cmyk color (unable to convert): " % colorValue); return; } quint8 c = cmyk >> 24 & 0xff; quint8 m = cmyk >> 16 & 0xff; quint8 y = cmyk >> 8 & 0xff; quint8 k = cmyk & 0xff; dbgPigment << "Color parsed: "<< c << m << y << k; currentColor.color.data()[0] = c; currentColor.color.data()[1] = m; currentColor.color.data()[2] = y; currentColor.color.data()[3] = k; currentColor.color.setOpacity(OPACITY_OPAQUE_U8); set->add(currentColor); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else { xml->raiseError("Unknown color space for color " + currentColor.name); } } bool loadScribusXmlPalette(KoColorSet *set, QXmlStreamReader *xml) { //1. Get name QXmlStreamAttributes paletteProperties = xml->attributes(); QStringRef paletteName = paletteProperties.value("Name"); dbgPigment << "Processed name of palette:" << paletteName; set->setName(paletteName.toString()); //2. Inside the SCRIBUSCOLORS, there are lots of colors. Retrieve them while(xml->readNextStartElement()) { QStringRef currentElement = xml->name(); if(QStringRef::compare(currentElement, "COLOR", Qt::CaseInsensitive) == 0) { scribusParseColor(set, xml); } else { xml->skipCurrentElement(); } } if(xml->hasError()) { return false; } return true; } bool KoColorSet::loadXml() { bool res = false; QXmlStreamReader *xml = new QXmlStreamReader(d->data); if (xml->readNextStartElement()) { QStringRef paletteId = xml->name(); if (QStringRef::compare(paletteId, "SCRIBUSCOLORS", Qt::CaseInsensitive) == 0) { // Scribus dbgPigment << "XML palette: " << filename() << ", Scribus format"; res = loadScribusXmlPalette(this, xml); } else { // Unknown XML format xml->raiseError("Unknown XML palette format. Expected SCRIBUSCOLORS, found " + paletteId); } } // If there is any error (it should be returned through the stream) if (xml->hasError() || !res) { warnPigment << "Illegal XML palette:" << filename(); warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString(); return false; } else { dbgPigment << "XML palette parsed successfully:" << filename(); return true; } } +bool KoColorSet::saveKpl(QIODevice *dev) const +{ + QScopedPointer store(KoStore::createStore(dev, KoStore::Write, "application/x-krita-palette", KoStore::Zip)); + if (!store || store->bad()) return false; + + QSet profiles; + QMap profileMap; + + { + QDomDocument doc; + QDomElement root = doc.createElement("Colorset"); + root.setAttribute("version", "1.0"); + root.setAttribute("name", name()); + root.setAttribute("comment", d->comment); + root.setAttribute("columns", d->columns); + Q_FOREACH(const KoColorSetEntry &entry, d->colors) { + + // Only save non-builtin profiles.= + const KoColorProfile *profile = entry.color.colorSpace()->profile(); + if (!profile->fileName().isEmpty()) { + profiles << profile; + profileMap[profile] = entry.color.colorSpace(); + } + QDomElement el = doc.createElement("ColorSetEntry"); + el.setAttribute("name", entry.name); + el.setAttribute("id", entry.id); + el.setAttribute("spot", entry.spotColor ? "true" : "false"); + el.setAttribute("bitdepth", entry.color.colorSpace()->colorDepthId().id()); + entry.color.toXML(doc, el); + root.appendChild(el); + } + Q_FOREACH(const QString &groupName, d->groupNames) { + QDomElement gl = doc.createElement("Group"); + gl.setAttribute("name", groupName); + root.appendChild(gl); + Q_FOREACH(const KoColorSetEntry &entry, d->groups.value(groupName)) { + + // Only save non-builtin profiles.= + const KoColorProfile *profile = entry.color.colorSpace()->profile(); + if (!profile->fileName().isEmpty()) { + profiles << profile; + profileMap[profile] = entry.color.colorSpace(); + } + QDomElement el = doc.createElement("ColorSetEntry"); + el.setAttribute("name", entry.name); + el.setAttribute("id", entry.id); + el.setAttribute("spot", entry.spotColor ? "true" : "false"); + el.setAttribute("bitdepth", entry.color.colorSpace()->colorDepthId().id()); + entry.color.toXML(doc, el); + gl.appendChild(el); + } + } + + doc.appendChild(root); + if (!store->open("colorset.xml")) { return false; } + QByteArray ba = doc.toByteArray(); + if (store->write(ba) != ba.size()) { return false; } + if (!store->close()) { return false; } + } + + QDomDocument doc; + QDomElement profileElement = doc.createElement("Profiles"); + + Q_FOREACH(const KoColorProfile *profile, profiles) { + QString fn = QFileInfo(profile->fileName()).fileName(); + if (!store->open(fn)) { return false; } + QByteArray profileRawData = profile->rawData(); + if (!store->write(profileRawData)) { return false; } + if (!store->close()) { return false; } + QDomElement el = doc.createElement("Profile"); + el.setAttribute("filename", fn); + el.setAttribute("name", profile->name()); + el.setAttribute("colorModelId", profileMap[profile]->colorModelId().id()); + el.setAttribute("colorDepthId", profileMap[profile]->colorDepthId().id()); + profileElement.appendChild(el); + + } + doc.appendChild(profileElement); + if (!store->open("profiles.xml")) { return false; } + QByteArray ba = doc.toByteArray(); + if (store->write(ba) != ba.size()) { return false; } + if (!store->close()) { return false; } + + return store->finalize(); +} + +bool KoColorSet::loadKpl() +{ + QBuffer buf(&d->data); + buf.open(QBuffer::ReadOnly); + + QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-krita-palette", KoStore::Zip)); + if (!store || store->bad()) return false; + + if (store->hasFile("profiles.xml")) { + + if (!store->open("profiles.xml")) { return false; } + QByteArray data; + data.resize(store->size()); + QByteArray ba = store->read(store->size()); + store->close(); + + QDomDocument doc; + doc.setContent(ba); + QDomElement e = doc.documentElement(); + QDomElement c = e.firstChildElement("Profiles"); + while (!c.isNull()) { + + QString name = c.attribute("name"); + QString filename = c.attribute("filename"); + QString colorModelId = c.attribute("colorModelId"); + QString colorDepthId = c.attribute("colorDepthId"); + if (!KoColorSpaceRegistry::instance()->profileByName(name)) { + store->open(filename); + QByteArray data; + data.resize(store->size()); + data = store->read(store->size()); + store->close(); + + const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(colorModelId, colorDepthId, data); + if (profile && profile->valid()) { + KoColorSpaceRegistry::instance()->addProfile(profile); + } + } + + c = c.nextSiblingElement(); + + } + } + + { + if (!store->open("colorset.xml")) { return false; } + QByteArray data; + data.resize(store->size()); + QByteArray ba = store->read(store->size()); + store->close(); + + QDomDocument doc; + doc.setContent(ba); + QDomElement e = doc.documentElement(); + setName(e.attribute("name")); + d->comment = e.attribute("comment"); + d->columns = e.attribute("columns").toInt(); + + QDomElement c = e.firstChildElement("ColorSetEntry"); + while (!c.isNull()) { + QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id()); + KoColorSetEntry entry; + + + entry.color = KoColor::fromXML(c.firstChildElement(), colorDepthId); + entry.name = c.attribute("name"); + entry.id = c.attribute("id"); + entry.spotColor = c.attribute("spot", "false") == "true" ? true : false; + d->colors << entry; + + c = c.nextSiblingElement("ColorSetEntry"); + + } + QDomElement g = e.firstChildElement("Group"); + while (!g.isNull()) { + QString groupName = g.attribute("name"); + addGroup(groupName); + QDomElement cg = g.firstChildElement("ColorSetEntry"); + while (!cg.isNull()) { + QString colorDepthId = cg.attribute("bitdepth", Integer8BitsColorDepthID.id()); + KoColorSetEntry entry; + + + entry.color = KoColor::fromXML(cg.firstChildElement(), colorDepthId); + entry.name = cg.attribute("name"); + entry.id = cg.attribute("id"); + entry.spotColor = cg.attribute("spot", "false") == "true" ? true : false; + add(entry, groupName); + + cg = cg.nextSiblingElement("ColorSetEntry"); + + } + g = g.nextSiblingElement("Group"); + } + + } + + + buf.close(); + return true; +} + quint16 readShort(QIODevice *io) { quint16 val; quint64 read = io->read((char*)&val, 2); if (read != 2) return false; return qFromBigEndian(val); } bool KoColorSet::loadAco() { QFileInfo info(filename()); setName(info.baseName()); QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); quint16 version = readShort(&buf); quint16 numColors = readShort(&buf); KoColorSetEntry e; if (version == 1 && buf.size() > 4+numColors*10) { buf.seek(4+numColors*10); version = readShort(&buf); numColors = readShort(&buf); } const quint16 quint16_MAX = 65535; for (int i = 0; i < numColors && !buf.atEnd(); ++i) { quint16 colorSpace = readShort(&buf); quint16 ch1 = readShort(&buf); quint16 ch2 = readShort(&buf); quint16 ch3 = readShort(&buf); quint16 ch4 = readShort(&buf); bool skip = false; if (colorSpace == 0) { // RGB - e.color = KoColor(KoColorSpaceRegistry::instance()->rgb16()); + const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); + e.color = KoColor(KoColorSpaceRegistry::instance()->rgb16(srgb)); reinterpret_cast(e.color.data())[0] = ch3; reinterpret_cast(e.color.data())[1] = ch2; reinterpret_cast(e.color.data())[2] = ch1; e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 1) { // HSB e.color = KoColor(KoColorSpaceRegistry::instance()->rgb16()); QColor c; c.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0); e.color.fromQColor(c); e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 2) { // CMYK e.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(e.color.data())[0] = quint16_MAX - ch1; reinterpret_cast(e.color.data())[1] = quint16_MAX - ch2; reinterpret_cast(e.color.data())[2] = quint16_MAX - ch3; reinterpret_cast(e.color.data())[3] = quint16_MAX - ch4; e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 7) { // LAB e.color = KoColor(KoColorSpaceRegistry::instance()->lab16()); reinterpret_cast(e.color.data())[0] = ch3; reinterpret_cast(e.color.data())[1] = ch2; reinterpret_cast(e.color.data())[2] = ch1; e.color.setOpacity(OPACITY_OPAQUE_U8); } else if (colorSpace == 8) { // GRAY e.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(e.color.data())[0] = ch1 * (quint16_MAX / 10000); e.color.setOpacity(OPACITY_OPAQUE_U8); } else { warnPigment << "Unsupported colorspace in palette" << filename() << "(" << colorSpace << ")"; skip = true; } if (version == 2) { quint16 v2 = readShort(&buf); //this isn't a version, it's a marker and needs to be skipped. Q_UNUSED(v2); quint16 size = readShort(&buf) -1; //then comes the length if (size>0) { QByteArray ba = buf.read(size*2); if (ba.size() == size*2) { QTextCodec *Utf16Codec = QTextCodec::codecForName("UTF-16BE"); e.name = Utf16Codec->toUnicode(ba); } else { warnPigment << "Version 2 name block is the wrong size" << filename(); } } v2 = readShort(&buf); //end marker also needs to be skipped. Q_UNUSED(v2); } if (!skip) { add(e); } } return true; } +bool KoColorSet::loadSbz() { + QBuffer buf(&d->data); + buf.open(QBuffer::ReadOnly); + + // &buf is a subclass of QIODevice + QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-swatchbook", KoStore::Zip)); + if (!store || store->bad()) return false; + + if (store->hasFile("swatchbook.xml")) { // Try opening... + + if (!store->open("swatchbook.xml")) { return false; } + QByteArray data; + data.resize(store->size()); + QByteArray ba = store->read(store->size()); + store->close(); + + dbgPigment << "XML palette: " << filename() << ", SwatchBooker format"; + + QDomDocument doc; + int errorLine, errorColumn; + QString errorMessage; + bool status = doc.setContent(ba, &errorMessage, &errorLine, &errorColumn); + if (!status) { + warnPigment << "Illegal XML palette:" << filename(); + warnPigment << "Error (line" << errorLine << ", column" << errorColumn << "):" << errorMessage; + return false; + } + + QDomElement e = doc.documentElement(); // SwatchBook + + // Start reading properties... + QDomElement metadata = e.firstChildElement("metadata"); + + if (e.isNull()) { + warnPigment << "Palette metadata not found"; + return false; + } + + QDomElement title = metadata.firstChildElement("dc:title"); + QString colorName = title.text(); + colorName = colorName.isEmpty() ? i18n("Untitled") : colorName; + setName(colorName); + dbgPigment << "Processed name of palette:" << name(); + // End reading properties + + // Now read colors... + QDomElement materials = e.firstChildElement("materials"); + if (materials.isNull()) { + warnPigment << "Materials (color definitions) not found"; + return false; + } + // This one has lots of "color" elements + QDomElement colorElement = materials.firstChildElement("color"); + if (colorElement.isNull()) { + warnPigment << "Color definitions not found (line" << materials.lineNumber() << ", column" << materials.columnNumber() << ")"; + return false; + } + + // Also read the swatch book... + QDomElement book = e.firstChildElement("book"); + if (book.isNull()) { + warnPigment << "Palette book (swatch composition) not found (line" << e.lineNumber() << ", column" << e.columnNumber() << ")"; + return false; + } + // Which has lots of "swatch"es (todo: support groups) + QDomElement swatch = book.firstChildElement(); + if (swatch.isNull()) { + warnPigment << "Swatches/groups definition not found (line" << book.lineNumber() << ", column" << book.columnNumber() << ")"; + return false; + } + + // We'll store colors here, and as we process swatches + // we'll add them to the palette + QHash materialsBook; + QHash fileColorSpaces; + + // Color processing + for(; !colorElement.isNull(); colorElement = colorElement.nextSiblingElement("color")) + { + KoColorSetEntry currentColor; + // Set if color is spot + currentColor.spotColor = colorElement.attribute("usage") == "spot"; + + // inside contains id and name + // one or more define the color + QDomElement currentColorMetadata = colorElement.firstChildElement("metadata"); + QDomNodeList currentColorValues = colorElement.elementsByTagName("values"); + // Get color name + QDomElement colorTitle = currentColorMetadata.firstChildElement("dc:title"); + QDomElement colorId = currentColorMetadata.firstChildElement("dc:identifier"); + // Is there an id? (we need that at the very least for identifying a color) + if (colorId.text().isEmpty()) { + warnPigment << "Unidentified color (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; + return false; + } + if (materialsBook.contains(colorId.text())) { + warnPigment << "Duplicated color definition (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")"; + return false; + } + + // Get a valid color name + currentColor.id = colorId.text(); + currentColor.name = colorTitle.text().isEmpty() ? colorId.text() : colorTitle.text(); + + // Get a valid color definition + if (currentColorValues.isEmpty()) { + warnPigment << "Color definitions not found (line" << colorElement.lineNumber() << ", column" << colorElement.columnNumber() << ")"; + return false; + } + + bool firstDefinition = false; + const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); + // Priority: Lab, otherwise the first definition found + for(int j = 0; j < currentColorValues.size(); j++) { + QDomNode colorValue = currentColorValues.at(j); + QDomElement colorValueE = colorValue.toElement(); + QString model = colorValueE.attribute("model", QString()); + + // sRGB,RGB,HSV,HSL,CMY,CMYK,nCLR: 0 -> 1 + // YIQ: Y 0 -> 1 : IQ -0.5 -> 0.5 + // Lab: L 0 -> 100 : ab -128 -> 127 + // XYZ: 0 -> ~100 + if (model == "Lab") { + QStringList lab = colorValueE.text().split(" "); + if (lab.length() != 3) { + warnPigment << "Invalid Lab color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; + } + float l = lab.at(0).toFloat(&status); + float a = lab.at(1).toFloat(&status); + float b = lab.at(2).toFloat(&status); + if (!status) { + warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; + } + + currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); + reinterpret_cast(currentColor.color.data())[0] = l; + reinterpret_cast(currentColor.color.data())[1] = a; + reinterpret_cast(currentColor.color.data())[2] = b; + currentColor.color.setOpacity(OPACITY_OPAQUE_F); + firstDefinition = true; + break; // Immediately add this one + } + else if (model == "sRGB" && !firstDefinition) { + QStringList rgb = colorValueE.text().split(" "); + if (rgb.length() != 3) { + warnPigment << "Invalid sRGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; + } + float r = rgb.at(0).toFloat(&status); + float g = rgb.at(1).toFloat(&status); + float b = rgb.at(2).toFloat(&status); + if (!status) { + warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; + } + + currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb)); + reinterpret_cast(currentColor.color.data())[0] = r; + reinterpret_cast(currentColor.color.data())[1] = g; + reinterpret_cast(currentColor.color.data())[2] = b; + currentColor.color.setOpacity(OPACITY_OPAQUE_F); + firstDefinition = true; + } + else if (model == "XYZ" && !firstDefinition) { + QStringList xyz = colorValueE.text().split(" "); + if (xyz.length() != 3) { + warnPigment << "Invalid XYZ color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; + } + float x = xyz.at(0).toFloat(&status); + float y = xyz.at(1).toFloat(&status); + float z = xyz.at(2).toFloat(&status); + if (!status) { + warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; + } + + currentColor.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); + reinterpret_cast(currentColor.color.data())[0] = x; + reinterpret_cast(currentColor.color.data())[1] = y; + reinterpret_cast(currentColor.color.data())[2] = z; + currentColor.color.setOpacity(OPACITY_OPAQUE_F); + firstDefinition = true; + } + // The following color spaces admit an ICC profile (in SwatchBooker) + else if (model == "CMYK" && !firstDefinition) { + QStringList cmyk = colorValueE.text().split(" "); + if (cmyk.length() != 4) { + warnPigment << "Invalid CMYK color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; + } + float c = cmyk.at(0).toFloat(&status); + float m = cmyk.at(1).toFloat(&status); + float y = cmyk.at(2).toFloat(&status); + float k = cmyk.at(3).toFloat(&status); + if (!status) { + warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; + } + + QString space = colorValueE.attribute("space"); + const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); + + if (!space.isEmpty()) { + // Try loading the profile and add it to the registry + if (!fileColorSpaces.contains(space)) { + store->enterDirectory("profiles"); + store->open(space); + QByteArray data; + data.resize(store->size()); + data = store->read(store->size()); + store->close(); + + const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); + if (profile && profile->valid()) { + KoColorSpaceRegistry::instance()->addProfile(profile); + colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); + fileColorSpaces.insert(space, colorSpace); + } + } + else { + colorSpace = fileColorSpaces.value(space); + } + } + + currentColor.color = KoColor(colorSpace); + reinterpret_cast(currentColor.color.data())[0] = c; + reinterpret_cast(currentColor.color.data())[1] = m; + reinterpret_cast(currentColor.color.data())[2] = y; + reinterpret_cast(currentColor.color.data())[3] = k; + currentColor.color.setOpacity(OPACITY_OPAQUE_F); + firstDefinition = true; + } + else if (model == "GRAY" && !firstDefinition) { + QString gray = colorValueE.text(); + + float g = gray.toFloat(&status); + if (!status) { + warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; + } + + QString space = colorValueE.attribute("space"); + const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Float32BitsColorDepthID.id(), QString()); + + if (!space.isEmpty()) { + // Try loading the profile and add it to the registry + if (!fileColorSpaces.contains(space)) { + store->enterDirectory("profiles"); + store->open(space); + QByteArray data; + data.resize(store->size()); + data = store->read(store->size()); + store->close(); + + const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data); + if (profile && profile->valid()) { + KoColorSpaceRegistry::instance()->addProfile(profile); + colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile); + fileColorSpaces.insert(space, colorSpace); + } + } + else { + colorSpace = fileColorSpaces.value(space); + } + } + + currentColor.color = KoColor(colorSpace); + reinterpret_cast(currentColor.color.data())[0] = g; + currentColor.color.setOpacity(OPACITY_OPAQUE_F); + firstDefinition = true; + } + else if (model == "RGB" && !firstDefinition) { + QStringList rgb = colorValueE.text().split(" "); + if (rgb.length() != 3) { + warnPigment << "Invalid RGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; + } + float r = rgb.at(0).toFloat(&status); + float g = rgb.at(1).toFloat(&status); + float b = rgb.at(2).toFloat(&status); + if (!status) { + warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")"; + } + + QString space = colorValueE.attribute("space"); + const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb); + + if (!space.isEmpty()) { + // Try loading the profile and add it to the registry + if (!fileColorSpaces.contains(space)) { + store->enterDirectory("profiles"); + store->open(space); + QByteArray data; + data.resize(store->size()); + data = store->read(store->size()); + store->close(); + + const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), data); + if (profile && profile->valid()) { + KoColorSpaceRegistry::instance()->addProfile(profile); + colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), profile); + fileColorSpaces.insert(space, colorSpace); + } + } + else { + colorSpace = fileColorSpaces.value(space); + } + } + + currentColor.color = KoColor(colorSpace); + reinterpret_cast(currentColor.color.data())[0] = r; + reinterpret_cast(currentColor.color.data())[1] = g; + reinterpret_cast(currentColor.color.data())[2] = b; + currentColor.color.setOpacity(OPACITY_OPAQUE_F); + firstDefinition = true; + } + else { + warnPigment << "Color space not implemented:" << model << "(line" << colorValueE.lineNumber() << ", column "<< colorValueE.columnNumber() << ")"; + } + } + if (firstDefinition) { + materialsBook.insert(currentColor.id, currentColor); + } + else { + warnPigment << "No supported color spaces for the current color (line" << colorElement.lineNumber() << ", column "<< colorElement.columnNumber() << ")"; + return false; + } + } + // End colors + // Now decide which ones will go into the palette + + for(;!swatch.isNull(); swatch = swatch.nextSiblingElement()) { + QString type = swatch.tagName(); + if (type.isEmpty() || type.isNull()) { + warnPigment << "Invalid swatch/group definition (no id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; + return false; + } + else if (type == "swatch") { + QString id = swatch.attribute("material"); + if (id.isEmpty() || id.isNull()) { + warnPigment << "Invalid swatch definition (no material id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; + return false; + } + if (materialsBook.contains(id)) { + add(materialsBook.value(id)); + } + else { + warnPigment << "Invalid swatch definition (material not found) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")"; + return false; + } + } + else if (type == "group") { + QDomElement groupMetadata = swatch.firstChildElement("metadata"); + if (groupMetadata.isNull()) { + warnPigment << "Invalid group definition (missing metadata) (line" << groupMetadata.lineNumber() << ", column" << groupMetadata.columnNumber() << ")"; + return false; + } + QDomElement groupTitle = metadata.firstChildElement("dc:title"); + if (groupTitle.isNull()) { + warnPigment << "Invalid group definition (missing title) (line" << groupTitle.lineNumber() << ", column" << groupTitle.columnNumber() << ")"; + return false; + } + QString currentGroupName = groupTitle.text(); + + QDomElement groupSwatch = swatch.firstChildElement("swatch"); + + while(!groupSwatch.isNull()) { + QString id = groupSwatch.attribute("material"); + if (id.isEmpty() || id.isNull()) { + warnPigment << "Invalid swatch definition (no material id) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; + return false; + } + if (materialsBook.contains(id)) { + add(materialsBook.value(id), currentGroupName); + } + else { + warnPigment << "Invalid swatch definition (material not found) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")"; + return false; + } + groupSwatch = groupSwatch.nextSiblingElement("swatch"); + } + } + } + // End palette + } + + buf.close(); + return true; +} diff --git a/libs/pigment/resources/KoColorSet.h b/libs/pigment/resources/KoColorSet.h index 18b307cdf8..6e7527b4cb 100644 --- a/libs/pigment/resources/KoColorSet.h +++ b/libs/pigment/resources/KoColorSet.h @@ -1,135 +1,217 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt + Copyright (c) 2016 L. E. Segovia This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KOCOLORSET #define KOCOLORSET #include #include #include #include #include #include "KoColor.h" struct KoColorSetEntry { KoColorSetEntry() {} KoColorSetEntry(const KoColor &_color, const QString &_name) : color(_color), name(_name) {} KoColor color; QString name; QString id; bool spotColor {false}; bool operator==(const KoColorSetEntry& rhs) const { return color == rhs.color && name == rhs.name; } }; /** * Open Gimp, Photoshop or RIFF palette files. This is a straight port * from the Gimp. */ class KRITAPIGMENT_EXPORT KoColorSet : public QObject, public KoResource { Q_OBJECT public: enum PaletteType { UNKNOWN = 0, GPL, // GIMP RIFF_PAL, // RIFF ACT, // Photoshop binary PSP_PAL, // PaintShop Pro ACO, // Photoshop Swatches - XML // XML palette (Scribus) + XML, // XML palette (Scribus) + KPL, // KoColor-based XML palette + SBZ // SwatchBooker }; /** * Load a color set from a file. This can be a Gimp - * palette, a RIFF palette or a Photoshop palette. + * palette, a RIFF palette, a Photoshop palette, + * a Scribus palette or a SwatchBooker palette. */ explicit KoColorSet(const QString &filename); /// Create an empty color set KoColorSet(); /// Explicit copy constructor (KoResource copy constructor is private) KoColorSet(const KoColorSet& rhs); virtual ~KoColorSet(); virtual bool load(); virtual bool loadFromDevice(QIODevice *dev); virtual bool save(); virtual bool saveToDevice(QIODevice* dev) const; virtual QString defaultFileExtension() const; void setColumnCount(int columns); int columnCount(); + /** + * @brief comment + * @return the comment. + */ + QString comment(); public: - void add(const KoColorSetEntry &); - void remove(const KoColorSetEntry &); - void removeAt(quint32 index); - KoColorSetEntry getColor(quint32 index); - qint32 nColors(); + /** + * @brief add Add a color to the palette. + * @param groupName color to add the group to. If empty, it will be added to the unsorted. + */ + void add(const KoColorSetEntry &, QString groupName = QString()); + + /** + * @brief insertBefore insert color before index into group. + * @param index + * @param groupName name of the group that the color goes into. + * @return new index of index after the prepending. + */ + quint32 insertBefore(const KoColorSetEntry &, qint32 index, const QString &groupName = QString()); + + void removeAt(quint32 index, QString groupName = QString()); + + /** + * @brief getColorGlobal + * A function for getting a color based on a global index. Useful for itterating through all color entries. + * @param globalIndex the global index over the whole palette. + * @return the entry. + */ + KoColorSetEntry getColorGlobal(quint32 globalIndex); + /** + * @brief getColorGroup + * A function for getting the color from a specific group. + * @param groupName the name of the group, will give unosrted when not defined. + * @param index the index within the group. + * @return the entry + */ + KoColorSetEntry getColorGroup(quint32 index, QString groupName = QString()); + + QString findGroupByGlobalIndex(quint32 globalIndex, quint32 *index); + QString findGroupByColorName(const QString &name, quint32 *index); + QString findGroupByID(const QString &id,quint32 *index); + + /** + * @brief getGroupNames + * @return returns a list of group names, excluding the unsorted group. + */ + QStringList getGroupNames(); + + /** + * @brief nColorsGroup + * @param groupName string name of the group, when not specified, returns unsorted colors. + * @return the amount of colors in this group. + */ + quint32 nColorsGroup(QString groupName = QString()); + /** + * @brief nColors + * @return total colors in palette. + */ + quint32 nColors(); + + /** + * @brief addGroup + * Adds a new group. + * @param groupName the name of the new group. When not specified, this will fail. + * @return whether thegroup was made. + */ + bool addGroup(const QString &groupName); + /** + * @brief removeGroup + * Remove a group from the KoColorSet + * @param groupName the name of the group you want to remove. + * @param keepColors Whether you wish to keep the colorsetentries. These will be added to the unsorted. + * @return whether it could find the group to remove. + */ + bool removeGroup(const QString &groupName, bool keepColors = true); + void clear(); /** * @brief getIndexClosestColor * function that matches the color to all colors in the colorset, and returns the index * of the closest match. * @param color the color you wish to compare. * @param useGivenColorSpace whether to use the color space of the color given * when the two colors' colorspaces don't match. Else it'll use the entry's colorspace. * @return returns the int of the closest match. */ - qint32 getIndexClosestColor(KoColor color, bool useGivenColorSpace = true); + quint32 getIndexClosestColor(KoColor color, bool useGivenColorSpace = true); /** * @brief closestColorName * convenience function to get the name of the closest match. * @param color * @param useGivenColorSpace * @return */ QString closestColorName(KoColor color, bool useGivenColorSpace = true); private: bool init(); + bool saveGpl(QIODevice *dev) const; bool loadGpl(); + bool loadAct(); bool loadRiff(); bool loadPsp(); bool loadAco(); bool loadXml(); + bool loadSbz(); + + bool saveKpl(QIODevice *dev) const; + bool loadKpl(); + + struct Private; const QScopedPointer d; }; #endif // KOCOLORSET diff --git a/libs/pigment/tests/TestKoColor.cpp b/libs/pigment/tests/TestKoColor.cpp index de4349d4e8..b5d3cf45a0 100644 --- a/libs/pigment/tests/TestKoColor.cpp +++ b/libs/pigment/tests/TestKoColor.cpp @@ -1,88 +1,88 @@ /* * Copyright (C) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this 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 "TestKoColor.h" #include #include #include "KoColorModelStandardIds.h" #include "KoColor.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "DebugPigment.h" bool nearEqualValue(int a, int b) { return qAbs(a - b) <= 1; } void TestKoColor::testForModel(QString model) { QColor qc(200, 125, 100); QList depthIDs = KoColorSpaceRegistry::instance()->colorDepthList(model, KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH (const KoID& depthId, depthIDs) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(model, depthId.id() , ""); if (cs) { KoColor kc(cs); kc.fromQColor(qc); QDomDocument doc; QDomElement elt = doc.createElement("color"); kc.toXML(doc, elt); doc.appendChild(elt); dbgPigment << doc.toString(); - KoColor kcu = KoColor::fromXML(elt.firstChildElement(), depthId.id(), QHash()); + KoColor kcu = KoColor::fromXML(elt.firstChildElement(), depthId.id()); QVERIFY2(*(kc.colorSpace()) == *(kcu.colorSpace()), QString("Not identical color space (colorModelId = %1 depthId = %2) != (colorModelId = %3 depthId = %4) ") .arg(kc.colorSpace()->colorModelId().id()) .arg(kc.colorSpace()->colorDepthId().id()) .arg(kcu.colorSpace()->colorModelId().id()) .arg(kcu.colorSpace()->colorDepthId().id()).toLatin1()); QVERIFY(cs->difference(kcu.data(), kc.data()) <= 1); } } } void TestKoColor::testSerialization() { testForModel(RGBAColorModelID.id()); testForModel(XYZAColorModelID.id()); testForModel(LABAColorModelID.id()); testForModel(CMYKAColorModelID.id()); testForModel(GrayAColorModelID.id()); // we cannot test ycbcr since we cannot ship profiles //testForModel(YCbCrAColorModelID.id()); } void TestKoColor::testConversion() { QColor c = Qt::red; const KoColorSpace *csOrig = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace *csDst = KoColorSpaceRegistry::instance()->lab16(); KoColor kc(csOrig); kc.fromQColor(c); kc.convertTo(csDst); } QTEST_GUILESS_MAIN(TestKoColor) diff --git a/libs/store/KoStore.cpp b/libs/store/KoStore.cpp index 0ca14d3469..41dfc034cd 100644 --- a/libs/store/KoStore.cpp +++ b/libs/store/KoStore.cpp @@ -1,519 +1,448 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2002 David Faure , Werner Trobin Copyright (C) 2010 C. Boemann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoStore.h" #include "KoStore_p.h" #include "KoZipStore.h" #include "KoDirectoryStore.h" #include #include #include #include #include #define DefaultFormat KoStore::Zip static KoStore::Backend determineBackend(QIODevice *dev) { unsigned char buf[5]; if (dev->read((char *)buf, 4) < 4) return DefaultFormat; // will create a "bad" store (bad()==true) if (buf[0] == 'P' && buf[1] == 'K' && buf[2] == 3 && buf[3] == 4) return KoStore::Zip; return DefaultFormat; // fallback } KoStore* KoStore::createStore(const QString& fileName, Mode mode, const QByteArray & appIdentification, Backend backend, bool writeMimetype) { if (backend == Auto) { if (mode == KoStore::Write) backend = DefaultFormat; else { QFileInfo inf(fileName); if (inf.isDir()) backend = Directory; else { QFile file(fileName); if (file.open(QIODevice::ReadOnly)) backend = determineBackend(&file); else backend = DefaultFormat; // will create a "bad" store (bad()==true) } } } switch (backend) { case Zip: return new KoZipStore(fileName, mode, appIdentification, writeMimetype); case Directory: return new KoDirectoryStore(fileName /* should be a dir name.... */, mode, writeMimetype); default: warnStore << "Unsupported backend requested for KoStore : " << backend; return 0; } } KoStore* KoStore::createStore(QIODevice *device, Mode mode, const QByteArray & appIdentification, Backend backend, bool writeMimetype) { if (backend == Auto) { if (mode == KoStore::Write) backend = DefaultFormat; else { if (device->open(QIODevice::ReadOnly)) { backend = determineBackend(device); device->close(); } } } switch (backend) { case Directory: errorStore << "Can't create a Directory store for a memory buffer!" << endl; // fallback case Zip: return new KoZipStore(device, mode, appIdentification, writeMimetype); default: warnStore << "Unsupported backend requested for KoStore : " << backend; return 0; } } KoStore* KoStore::createStore(const QUrl &url, Mode mode, const QByteArray & appIdentification, Backend backend, bool writeMimetype) { Q_ASSERT(url.isLocalFile()); return createStore(url.toLocalFile(), mode, appIdentification, backend, writeMimetype); } namespace { const char ROOTPART[] = "root"; const char MAINNAME[] = "maindoc.xml"; } KoStore::KoStore(Mode mode, bool writeMimetype) : d_ptr(new KoStorePrivate(this, mode, writeMimetype)) {} KoStore::~KoStore() { Q_D(KoStore); delete d->stream; delete d_ptr; } bool KoStore::open(const QString & _name) { Q_D(KoStore); // This also converts from relative to absolute, i.e. merges the currentPath() d->fileName = d->toExternalNaming(_name); if (d->isOpen) { warnStore << "Store is already opened, missing close"; return false; } if (d->fileName.length() > 512) { errorStore << "KoStore: Filename " << d->fileName << " is too long" << endl; return false; } if (d->mode == Write) { debugStore << "opening for writing" << d->fileName; if (d->filesList.contains(d->fileName)) { warnStore << "KoStore: Duplicate filename" << d->fileName; return false; } d->filesList.append(d->fileName); d->size = 0; if (!openWrite(d->fileName)) return false; } else if (d->mode == Read) { debugStore << "Opening for reading" << d->fileName; if (!openRead(d->fileName)) return false; } else return false; d->isOpen = true; return true; } bool KoStore::isOpen() const { Q_D(const KoStore); return d->isOpen; } bool KoStore::close() { Q_D(KoStore); debugStore << "Closing"; if (!d->isOpen) { warnStore << "You must open before closing"; return false; } bool ret = d->mode == Write ? closeWrite() : closeRead(); delete d->stream; d->stream = 0; d->isOpen = false; return ret; } QIODevice* KoStore::device() const { Q_D(const KoStore); if (!d->isOpen) warnStore << "You must open before asking for a device"; if (d->mode != Read) warnStore << "Can not get device from store that is opened for writing"; return d->stream; } QByteArray KoStore::read(qint64 max) { Q_D(KoStore); QByteArray data; if (!d->isOpen) { warnStore << "You must open before reading"; return data; } if (d->mode != Read) { errorStore << "KoStore: Can not read from store that is opened for writing" << endl; return data; } return d->stream->read(max); } qint64 KoStore::write(const QByteArray& data) { return write(data.constData(), data.size()); // see below } qint64 KoStore::read(char *_buffer, qint64 _len) { Q_D(KoStore); if (!d->isOpen) { errorStore << "KoStore: You must open before reading" << endl; return -1; } if (d->mode != Read) { errorStore << "KoStore: Can not read from store that is opened for writing" << endl; return -1; } return d->stream->read(_buffer, _len); } qint64 KoStore::write(const char* _data, qint64 _len) { Q_D(KoStore); if (_len == 0) return 0; if (!d->isOpen) { errorStore << "KoStore: You must open before writing" << endl; return 0; } if (d->mode != Write) { errorStore << "KoStore: Can not write to store that is opened for reading" << endl; return 0; } int nwritten = d->stream->write(_data, _len); Q_ASSERT(nwritten == (int)_len); d->size += nwritten; return nwritten; } qint64 KoStore::size() const { Q_D(const KoStore); if (!d->isOpen) { warnStore << "You must open before asking for a size"; return static_cast(-1); } if (d->mode != Read) { warnStore << "Can not get size from store that is opened for writing"; return static_cast(-1); } return d->size; } bool KoStore::enterDirectory(const QString &directory) { Q_D(KoStore); //debugStore <<"enterDirectory" << directory; int pos; bool success = true; QString tmp(directory); while ((pos = tmp.indexOf('/')) != -1 && (success = d->enterDirectoryInternal(tmp.left(pos)))) tmp.remove(0, pos + 1); if (success && !tmp.isEmpty()) return d->enterDirectoryInternal(tmp); return success; } bool KoStore::leaveDirectory() { Q_D(KoStore); if (d->currentPath.isEmpty()) return false; d->currentPath.pop_back(); return enterAbsoluteDirectory(currentPath()); } QString KoStore::currentPath() const { Q_D(const KoStore); QString path; QStringList::ConstIterator it = d->currentPath.begin(); QStringList::ConstIterator end = d->currentPath.end(); for (; it != end; ++it) { path += *it; path += '/'; } return path; } void KoStore::pushDirectory() { Q_D(KoStore); d->directoryStack.push(currentPath()); } void KoStore::popDirectory() { Q_D(KoStore); d->currentPath.clear(); enterAbsoluteDirectory(QString()); enterDirectory(d->directoryStack.pop()); } -bool KoStore::addLocalFile(const QString &fileName, const QString &destName) -{ - QFileInfo fi(fileName); - uint size = fi.size(); - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) { - return false; - } - - if (!open(destName)) { - return false; - } - - QByteArray data; - data.resize(8 * 1024); - - uint total = 0; - for (int block = 0; (block = file.read(data.data(), data.size())) > 0; total += block) { - data.resize(block); - if (write(data) != block) - return false; - data.resize(8*1024); - } - Q_ASSERT(total == size); - if (total != size) { - warnStore << "Did not write enough bytes. Expected: " << size << ", wrote" << total; - return false; - } - - close(); - file.close(); - - return true; -} - -bool KoStore::addDataToFile(QByteArray &buffer, const QString &destName) -{ - QBuffer file(&buffer); - if (!file.open(QIODevice::ReadOnly)) { - return false; - } - - if (!open(destName)) { - return false; - } - - QByteArray data; - data.resize(8 * 1024); - - uint total = 0; - for (int block = 0; (block = file.read(data.data(), data.size())) > 0; total += block) { - data.resize(block); - if (write(data) != block) - return false; - data.resize(8*1024); - } - - close(); - file.close(); - - return true; -} - -bool KoStore::extractFile(const QString &srcName, const QString &fileName) -{ - Q_D(KoStore); - QFile file(fileName); - return d->extractFile(srcName, file); -} - - bool KoStore::extractFile(const QString &srcName, QByteArray &data) { Q_D(KoStore); QBuffer buffer(&data); return d->extractFile(srcName, buffer); } bool KoStorePrivate::extractFile(const QString &srcName, QIODevice &buffer) { if (!q->open(srcName)) return false; if (!buffer.open(QIODevice::WriteOnly)) { q->close(); return false; } // ### This could use KArchive::copy or something, no? QByteArray data; data.resize(8 * 1024); uint total = 0; for (int block = 0; (block = q->read(data.data(), data.size())) > 0; total += block) { buffer.write(data.data(), block); } if (q->size() != static_cast(-1)) Q_ASSERT(total == q->size()); buffer.close(); q->close(); return true; } bool KoStore::seek(qint64 pos) { Q_D(KoStore); return d->stream->seek(pos); } qint64 KoStore::pos() const { Q_D(const KoStore); return d->stream->pos(); } bool KoStore::atEnd() const { Q_D(const KoStore); return d->stream->atEnd(); } // See the specification for details of what this function does. QString KoStorePrivate::toExternalNaming(const QString & _internalNaming) const { if (_internalNaming == ROOTPART) return q->currentPath() + MAINNAME; QString intern; if (_internalNaming.startsWith("tar:/")) // absolute reference intern = _internalNaming.mid(5); // remove protocol else intern = q->currentPath() + _internalNaming; return intern; } bool KoStorePrivate::enterDirectoryInternal(const QString &directory) { if (q->enterRelativeDirectory(directory)) { currentPath.append(directory); return true; } return false; } bool KoStore::hasFile(const QString& fileName) const { Q_D(const KoStore); return fileExists(d->toExternalNaming(fileName)); } bool KoStore::finalize() { Q_D(KoStore); Q_ASSERT(!d->finalized); // call this only once! d->finalized = true; return doFinalize(); } void KoStore::setCompressionEnabled(bool /*e*/) { } bool KoStore::isEncrypted() { return false; } bool KoStore::setPassword(const QString& /*password*/) { return false; } QString KoStore::password() { return QString(); } bool KoStore::bad() const { Q_D(const KoStore); return !d->good; } KoStore::Mode KoStore::mode() const { Q_D(const KoStore); return d->mode; } QStringList KoStore::directoryList() const { return QStringList(); } diff --git a/libs/store/KoStore.h b/libs/store/KoStore.h index 94a2ba0769..ae5370e46e 100644 --- a/libs/store/KoStore.h +++ b/libs/store/KoStore.h @@ -1,372 +1,351 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 David Faure Copyright (C) 2010 C. Boemann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __koStore_h_ #define __koStore_h_ #include #include #include "kritastore_export.h" class QWidget; class QUrl; class KoStorePrivate; /** * Saves and loads Krita documents using various backends. Currently supported * backends are zip and directory. * We call a "store" the file on the hard disk (the one the users sees) * and call a "file" a file inside the store. */ class KRITASTORE_EXPORT KoStore { public: enum Mode { Read, Write }; enum Backend { Auto, Zip, Directory }; /** * Open a store (i.e. the representation on disk of a Krita document). * * @param fileName the name of the file to open * @param mode if KoStore::Read, open an existing store to read it. * if KoStore::Write, create or replace a store. * @param backend the backend to use for the data storage. * Auto means automatically-determined for reading, * and the current format (now Zip) for writing. * * @param appIdentification the application's mimetype, * to be written in the file for "mime-magic" identification. * Only meaningful if mode is Write, and if backend!=Directory. * * @param writeMimetype If true, some backends (notably the Zip * store) will write a file called 'mimetype' automatically and * fill it with data from the appIdentification. This is only * applicable if Mode is set to Write. */ static KoStore *createStore(const QString &fileName, Mode mode, const QByteArray &appIdentification = QByteArray(), Backend backend = Auto, bool writeMimetype = true); /** * Create a store for any kind of QIODevice: file, memory buffer... * KoStore will take care of opening the QIODevice. * This method doesn't support the Directory store! */ static KoStore *createStore(QIODevice *device, Mode mode, const QByteArray &appIdentification = QByteArray(), Backend backend = Auto, bool writeMimetype = true); /** * Open a store (i.e. the representation on disk of a Krita document). * * @param url URL of the file to open * @param mode if KoStore::Read, open an existing store to read it. * if KoStore::Write, create or replace a store. * @param backend the backend to use for the data storage. * Auto means automatically-determined for reading, * and the current format (now Zip) for writing. * * @param appIdentification the application's mimetype, * to be written in the file for "mime-magic" identification. * Only meaningful if mode is Write, and if backend!=Directory. * * If the file is remote, the backend Directory cannot be used! * * @param writeMimetype If true, some backends (notably the Zip * store) will write a file called 'mimetype' automatically and * fill it with data from the appIdentification. This is only * applicable if Mode is set to Write. * * @bug saving not completely implemented (fixed temporary file) */ static KoStore *createStore(const QUrl &url, Mode mode, const QByteArray &appIdentification = QByteArray(), Backend backend = Auto, bool writeMimetype = true); /** * Destroys the store (i.e. closes the file on the hard disk) */ virtual ~KoStore(); /** * Open a new file inside the store * @param name The filename, internal representation ("root", "tar:/0"... ). * If the tar:/ prefix is missing it's assumed to be a relative URI. * @return true on success. */ bool open(const QString &name); /** * Check whether a file inside the store is currently opened with open(), * ready to be read or written. * @return true if a file is currently opened. */ bool isOpen() const; /** * Close the file inside the store * @return true on success. */ bool close(); /** * Get a device for reading a file from the store directly * (slightly faster than read() calls) * You need to call @ref open first, and @ref close afterwards. */ QIODevice *device() const; /** * Read data from the currently opened file. You can also use the streams * for this. */ QByteArray read(qint64 max); /** * Write data into the currently opened file. You can also use the streams * for this. */ qint64 write(const QByteArray &data); /** * Read data from the currently opened file. You can also use the streams * for this. * @return size of data read, -1 on error */ qint64 read(char *buffer, qint64 length); /** * Write data into the currently opened file. You can also use the streams * for this. */ virtual qint64 write(const char* data, qint64 length); /** * @return the size of the currently opened file, -1 on error. * Can be used as an argument for the read methods, for instance */ qint64 size() const; /** * @return true if an error occurred */ bool bad() const; /** * @return the mode used when opening, read or write */ Mode mode() const; /** * If an store is opened for reading, then the directories * of the store can be accessed via this function. * * @return a stringlist with all directories found */ virtual QStringList directoryList() const; /** * Enters one or multiple directories. In Read mode this actually * checks whether the specified directories exist and returns false * if they don't. In Write mode we don't create the directory, we * just use the "current directory" to generate the absolute path * if you pass a relative path (one not starting with tar:/) when * opening a stream. * Note: Operates on internal names */ bool enterDirectory(const QString &directory); /** * Leaves a directory. Equivalent to "cd .." * @return true on success, false if we were at the root already to * make it possible to "loop to the root" */ bool leaveDirectory(); /** * Returns the current path including a trailing slash. * Note: Returns a path in "internal name" style */ QString currentPath() const; /** * Stacks the current directory. Restore the current path using * @ref popDirectory . */ void pushDirectory(); /** * Restores the previously pushed directory. No-op if the stack is * empty. */ void popDirectory(); /** * @return true if the given file exists in the current directory, * i.e. if open(fileName) will work. */ bool hasFile(const QString &fileName) const; - /** - * Imports a local file into a store - * @param fileName file on hard disk - * @param destName file in the store - */ - bool addLocalFile(const QString &fileName, const QString &destName); - - /** - * Imports data into a store - * @param buffer data - * @param destName file in the store - */ - bool addDataToFile(QByteArray &buffer, const QString &destName); - - /** - * Extracts a file out of the store - * @param sourceName file in the store - * @param fileName file on a disk - */ - bool extractFile(const QString &sourceName, const QString &fileName); - /** * Extracts a file out of the store to a buffer * @param sourceName file in the store * @param data memory buffer */ bool extractFile(const QString &sourceName, QByteArray &data); //@{ /// See QIODevice bool seek(qint64 pos); qint64 pos() const; bool atEnd() const; //@} /** * Call this before destroying the store, to be able to catch errors * (e.g. from ksavefile) */ bool finalize(); /** * Sets the password to be used for decryption or encryption of the store. * Use of this function is optional: an encryptable store should make * a best effort in obtaining a password if it wasn't supplied. * * This method only works before opening a file. It might fail when a file * has already been opened before calling this method. * * This method will not function for any store that is not encrypted or * can't be encrypted when saving. * * @param password A non-empty password. * * @return True if the password was set. */ virtual bool setPassword(const QString &password); /** * Retrieves the password used to encrypt or decrypt the store. Note that * QString() will returned if no password has been given or the store is * not encrypted. * * @return The password this store is encrypted with. */ virtual QString password(); /** * Returns whether a store opened for reading is encrypted or a store opened * for saving will be encrypted. * * @return True if the store is encrypted. */ virtual bool isEncrypted(); /** * Allow to enable or disable compression of the files. Only supported by the * ZIP backend. */ virtual void setCompressionEnabled(bool e); protected: KoStore(Mode mode, bool writeMimetype = true); /** * Finalize store - called by finalize. * @return true on success */ virtual bool doFinalize() { return true; } /** * Open the file @p name in the store, for writing * On success, this method must set m_stream to a stream in which we can write. * @param name "absolute path" (in the archive) to the file to open * @return true on success */ virtual bool openWrite(const QString &name) = 0; /** * Open the file @p name in the store, for reading. * On success, this method must set m_stream to a stream from which we can read, * as well as setting m_iSize to the size of the file. * @param name "absolute path" (in the archive) to the file to open * @return true on success */ virtual bool openRead(const QString &name) = 0; /** * @return true on success */ virtual bool closeRead() = 0; /** * @return true on success */ virtual bool closeWrite() = 0; /** * Enter a subdirectory of the current directory. * The directory might not exist yet in Write mode. */ virtual bool enterRelativeDirectory(const QString &dirName) = 0; /** * Enter a directory where we've been before. * It is guaranteed to always exist. */ virtual bool enterAbsoluteDirectory(const QString &path) = 0; /** * Check if a file exists inside the store. * @param absPath the absolute path inside the store, i.e. not relative to the current directory */ virtual bool fileExists(const QString &absPath) const = 0; protected: KoStorePrivate *d_ptr; private: Q_DECLARE_PRIVATE(KoStore) private: KoStore(const KoStore& store); ///< don't copy KoStore& operator=(const KoStore& store); ///< don't assign }; #endif diff --git a/libs/ui/KisColorsetChooser.cpp b/libs/ui/KisColorsetChooser.cpp index f006d8fb0e..f74692162d 100644 --- a/libs/ui/KisColorsetChooser.cpp +++ b/libs/ui/KisColorsetChooser.cpp @@ -1,160 +1,160 @@ /* This file is part of the KDE project * Copyright (C) 2013 Sven Langkamp * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisColorsetChooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_resource_server_provider.h" #include "KisViewManager.h" #include #include #include #include #include #include "kis_int_parse_spin_box.h" class ColorSetDelegate : public QAbstractItemDelegate { public: ColorSetDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) {} ~ColorSetDelegate() 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 ColorSetDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { painter->save(); if (! index.isValid()) return; KoResource* resource = static_cast(index.internalPointer()); KoColorSet* colorSet = static_cast(resource); if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.highlight()); painter->setPen(option.palette.highlightedText().color()); } else { painter->setBrush(option.palette.text().color()); } painter->drawText(option.rect.x() + 5, option.rect.y() + painter->fontMetrics().ascent() + 5, colorSet->name()); int size = 7; - for (int i = 0; i < colorSet->nColors() && i*size < option.rect.width(); i++) { + for (quint32 i = 0; i < colorSet->nColors() && i*size < (quint32)option.rect.width(); i++) { QRect rect(option.rect.x() + i*size, option.rect.y() + option.rect.height() - size, size, size); - painter->fillRect(rect, colorSet->getColor(i).color.toQColor()); + painter->fillRect(rect, colorSet->getColorGlobal(i).color.toQColor()); } painter->restore(); } KisColorsetChooser::KisColorsetChooser(QWidget* parent): QWidget(parent) { KoResourceServer * rserver = KoResourceServerProvider::instance()->paletteServer(false); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); m_itemChooser = new KoResourceItemChooser(adapter, this); m_itemChooser->setItemDelegate(new ColorSetDelegate(this)); m_itemChooser->showTaggingBar(true); m_itemChooser->setFixedSize(250, 250); m_itemChooser->setRowHeight(30); m_itemChooser->setColumnCount(1); connect(m_itemChooser, SIGNAL(resourceSelected(KoResource*)), this, SLOT(resourceSelected(KoResource*))); QPushButton* saveButton = new QPushButton(i18n("Save")); connect(saveButton, SIGNAL(clicked(bool)), this, SLOT(slotSave())); m_nameEdit = new QLineEdit(this); m_nameEdit->setPlaceholderText(i18n("Insert name")); m_nameEdit->setClearButtonEnabled(true); m_columnEdit = new KisIntParseSpinBox(this); m_columnEdit->setRange(1, 30); m_columnEdit->setValue(10); QGridLayout* layout = new QGridLayout(this); layout->addWidget(m_itemChooser, 0, 0, 1, 3); layout->addWidget(new QLabel(i18n("Name:"), this), 1, 0, 1, 1); layout->addWidget(m_nameEdit, 1, 1, 1, 2); layout->addWidget(new QLabel(i18n("Columns:"), this), 2, 0, 1, 1); layout->addWidget(m_columnEdit, 2, 1, 1, 1); layout->addWidget(saveButton, 2, 2, 1, 1); layout->setColumnStretch(1, 1); } KisColorsetChooser::~KisColorsetChooser() { } void KisColorsetChooser::resourceSelected(KoResource* resource) { emit paletteSelected(static_cast(resource)); } void KisColorsetChooser::slotSave() { KoResourceServer * rserver = KoResourceServerProvider::instance()->paletteServer(); KoColorSet* colorset = new KoColorSet(); colorset->setValid(true); QString saveLocation = rserver->saveLocation(); QString name = m_nameEdit->text(); int columns = m_columnEdit->value(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Palette"); } QFileInfo fileInfo(saveLocation + name + colorset->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + colorset->defaultFileExtension()); i++; } colorset->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Palette %1", i); } colorset->setName(name); colorset->setColumnCount(columns); rserver->addResource(colorset); } diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 6c59f0371e..a96ca4f0a2 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1721 +1,1780 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "kis_resource_server_provider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisPart.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; /********************************************************** * * KisDocument * **********************************************************/ namespace { class DocumentProgressProxy : public KoProgressProxy { public: KisMainWindow *m_mainWindow; DocumentProgressProxy(KisMainWindow *mainWindow) : m_mainWindow(mainWindow) { } ~DocumentProgressProxy() override { // signal that the job is done setValue(-1); } int maximum() const override { return 100; } void setValue(int value) override { if (m_mainWindow) { m_mainWindow->slotProgress(value); } } void setRange(int /*minimum*/, int /*maximum*/) override { } void setFormat(const QString &/*format*/) override { } }; } //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private() : docInfo(0), progressUpdater(0), progressProxy(0), importExportManager(0), isImporting(false), isExporting(false), password(QString()), modifiedAfterAutosave(false), isAutosaving(false), backupFile(true), doNotSaveExtDoc(false), undoStack(0), m_saveOk(false), m_waitForSave(false), m_duringSaveAs(false), m_bAutoDetectedMime(false), modified(false), readwrite(true), disregardAutosaveFailure(false), nserver(0), macroNestDepth(0), imageIdleWatcher(2000 /*ms*/), suppressProgress(false), fileProgressProxy(0), savingLock(&savingMutex) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo; KoProgressUpdater *progressUpdater; KoProgressProxy *progressProxy; KoUnit unit; KisImportExportManager *importExportManager; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving bool isImporting; bool isExporting; // File --> Import/Export vs File --> Open/Save QString password; // The password used to encrypt an encrypted document QTimer autoSaveTimer; QString lastErrorMessage; // see openFile() + QString lastWarningMessage; int autoSaveDelay {300}; // in seconds, 0 to disable. bool modifiedAfterAutosave; bool isAutosaving; bool backupFile; bool doNotSaveExtDoc; // makes it possible to save only internally stored child documents KUndo2Stack *undoStack; KisGuidesConfig guidesConfig; QUrl m_originalURL; // for saveAs QString m_originalFilePath; // for saveAs bool m_saveOk; bool m_waitForSave; bool m_duringSaveAs; bool m_bAutoDetectedMime; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QEventLoop m_eventLoop; QMutex savingMutex; bool modified; bool readwrite; QDateTime firstMod; QDateTime lastMod; bool disregardAutosaveFailure; KisNameServer *nserver; qint32 macroNestDepth; KisImageSP image; KisImageSP savingImage; KisNodeSP preActivatedNode; KisShapeController* shapeController; KoShapeController* koShapeController; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; bool suppressProgress; KoProgressProxy* fileProgressProxy; QList assistants; KisGridConfig gridConfig; StdLockableWrapper savingLock; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class SafeSavingLocker; }; class KisDocument::Private::SafeSavingLocker { public: SafeSavingLocker(KisDocument::Private *_d, KisDocument *document) : d(_d) , m_document(document) , m_locked(false) , m_imageLock(d->image, true) { const int realAutoSaveInterval = KisConfig().autoSaveInterval(); const int emergencyAutoSaveInterval = 10; // sec /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, d->savingLock) < 0; if (!m_locked) { if (d->isAutosaving) { d->disregardAutosaveFailure = true; if (realAutoSaveInterval) { m_document->setAutoSaveDelay(emergencyAutoSaveInterval); } } else { d->image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, d->savingLock) < 0; } } if (m_locked) { d->disregardAutosaveFailure = false; } } ~SafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); d->savingLock.unlock(); const int realAutoSaveInterval = KisConfig().autoSaveInterval(); m_document->setAutoSaveDelay(realAutoSaveInterval); } } bool successfullyLocked() const { return m_locked; } private: KisDocument::Private *d; KisDocument *m_document; bool m_locked; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private()) { d->undoStack = new UndoStack(this); d->undoStack->setParent(this); d->importExportManager = new KisImportExportManager(this); d->importExportManager->setProgresUpdater(d->progressUpdater); connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); KisConfig cfg; setAutoSaveDelay(cfg.autoSaveInterval()); setObjectName(newObjectName()); d->docInfo = new KoDocumentInfo(this); d->firstMod = QDateTime::currentDateTime(); d->lastMod = QDateTime::currentDateTime(); // preload the krita resources KisResourceServerProvider::instance(); d->nserver = new KisNameServer(1); d->shapeController = new KisShapeController(this, d->nserver); d->koShapeController = new KoShapeController(0, d->shapeController); undoStack()->setUndoLimit(KisConfig().undoStackLimit()); connect(d->undoStack, SIGNAL(indexChanged(int)), this, SLOT(slotUndoStackIndexChanged(int))); setBackupFile(KisConfig().backupFile()); } KisDocument::~KisDocument() { /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer.disconnect(this); d->autoSaveTimer.stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } bool KisDocument::exportDocument(const QUrl &_url, KisPropertiesConfigurationSP exportConfiguration) { //qDebug() << "exportDocument" << _url.toDisplayString() << "is autosaving" << d->isAutosaving; bool ret; d->isExporting = true; // // Preserve a lot of state here because we need to restore it in order to // be able to fake a File --> Export. Can't do this in saveFile() because, // for a start, KParts has already set url and m_file and because we need // to restore the modified flag etc. and don't want to put a load on anyone // reimplementing saveFile() (Note: importDocument() and exportDocument() // will remain non-virtual). // QUrl oldURL = url(); QString oldFile = localFilePath(); //qDebug() << "\toldUrl" << oldURL << "oldFile" << oldFile << "export url" << _url; bool wasModified = isModified(); // save... ret = saveAs(_url, exportConfiguration); // // This is sooooo hacky :( // Hopefully we will restore enough state. // dbgUI << "Restoring KisDocument state to before export"; // always restore url & m_file regardless of failure or success //qDebug() << "\tafter saveAs: url" << url() << "local file path" << localFilePath(); setUrl(oldURL); setLocalFilePath(oldFile); //qDebug() << "\tafter restoring: url" << url() << "local file path" << localFilePath(); // on successful export we need to restore modified etc. too // on failed export, mimetype/modified hasn't changed anyway if (ret) { setModified(wasModified); } d->isExporting = false; return ret; } bool KisDocument::saveAs(const QUrl &url, KisPropertiesConfigurationSP exportConfiguration) { //qDebug() << "saveAs" << url; if (!url.isValid() || !url.isLocalFile()) { errKrita << "saveAs: Malformed URL " << url.url() << endl; return false; } d->m_duringSaveAs = true; d->m_originalURL = d->m_url; d->m_originalFilePath = d->m_file; d->m_url = url; // Store where to upload in saveToURL d->m_file = d->m_url.toLocalFile(); bool result = save(exportConfiguration); // Save local file and upload local file if (!result) { d->m_url = d->m_originalURL; d->m_file = d->m_originalFilePath; d->m_duringSaveAs = false; d->m_originalURL = QUrl(); d->m_originalFilePath.clear(); } return result; } bool KisDocument::save(KisPropertiesConfigurationSP exportConfiguration) { //qDebug() << "save" << d->m_file << d->m_url << url() << localFilePath(); d->m_saveOk = false; if (d->m_file.isEmpty()) { // document was created empty d->m_file = d->m_url.toLocalFile(); } updateEditingTime(true); setFileProgressProxy(); setUrl(url()); bool ok = saveFile(localFilePath(), exportConfiguration); clearFileProgressProxy(); if (ok) { setModified( false ); emit completed(); d->m_saveOk = true; d->m_duringSaveAs = false; d->m_originalURL = QUrl(); d->m_originalFilePath.clear(); return true; // Nothing to do } else { emit canceled(QString()); } return false; } bool KisDocument::saveFile(const QString &filePath, KisPropertiesConfigurationSP exportConfiguration) { if (!prepareLocksForSaving()) { return false; } // Unset the error message setErrorMessage(""); // Save it to be able to restore it after a failed save const bool wasModified = isModified(); // The output format is set by KisMainWindow, and by openFile QByteArray outputMimeType = d->outputMimeType; if (outputMimeType.isEmpty()) { outputMimeType = d->outputMimeType = nativeFormatMimeType(); } //qDebug() << "saveFile. Is Autosaving?" << isAutosaving() << "url" << filePath << d->outputMimeType; if (d->backupFile) { Q_ASSERT(url().isLocalFile()); KBackup::backupFile(url().toLocalFile()); } qApp->processEvents(); bool ret = false; bool suppressErrorDialog = false; KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK; setFileProgressUpdater(i18n("Saving Document")); QFileInfo fi(filePath); QString tempororaryFileName; { QTemporaryFile tf(QDir::tempPath() + "/XXXXXX" + fi.baseName() + "." + fi.completeSuffix()); tf.open(); tempororaryFileName = tf.fileName(); } Q_ASSERT(!tempororaryFileName.isEmpty()); //qDebug() << "saving to tempory file" << tempororaryFileName; status = d->importExportManager->exportDocument(tempororaryFileName, filePath, outputMimeType, !d->isExporting , exportConfiguration); ret = (status == KisImportExportFilter::OK); suppressErrorDialog = (isAutosaving() || status == KisImportExportFilter::UserCancelled || status == KisImportExportFilter::BadConversionGraph); //qDebug() << "Export status was" << status; if (ret) { //qDebug() << "copying temporary file" << tempororaryFileName << "to" << filePath; if (!d->isAutosaving && !d->suppressProgress) { QPointer updater = d->progressUpdater->startSubtask(1, "clear undo stack"); updater->setProgress(0); d->undoStack->setClean(); updater->setProgress(100); } else { d->undoStack->setClean(); } QFile tempFile(tempororaryFileName); QString s = filePath; QFile dstFile(s); while (QFileInfo(s).exists()) { s.append("_"); } bool r; if (s != filePath) { r = dstFile.rename(s); if (!r) { setErrorMessage(i18n("Could not rename original file to %1: %2", dstFile.fileName(), dstFile. errorString())); ret = false; } } if (tempFile.exists()) { r = tempFile.copy(filePath); if (!r) { setErrorMessage(i18n("Copying the temporary file failed: %1 to %2: %3", tempFile.fileName(), dstFile.fileName(), tempFile.errorString())); ret = false; } else { r = tempFile.remove(); if (!r) { setErrorMessage(i18n("Could not remove temporary file %1: %2", tempFile.fileName(), tempFile.errorString())); ret = false; } else if (s != filePath) { r = dstFile.remove(); if (!r) { setErrorMessage(i18n("Could not remove saved original file: %1", dstFile.errorString())); ret = false; } } } } else { setErrorMessage(i18n("The temporary file %1 is gone before we could copy it!", tempFile.fileName())); ret = false; } if (errorMessage().isEmpty()) { if (!isAutosaving()) { removeAutoSaveFiles(); } } else { ret = false; qWarning() << "Error while saving:" << errorMessage(); } // Restart the autosave timer // (we don't want to autosave again 2 seconds after a real save) if (!isAutosaving()) { setAutoSaveDelay(d->autoSaveDelay); } d->mimeType = outputMimeType; } if (!ret) { if (!suppressErrorDialog) { if (errorMessage().isEmpty()) { setErrorMessage(KisImportExportFilter::conversionStatusString(status)); } if (errorMessage().isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save\n%1", filePath)); } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage())); } } // couldn't save file so this new URL is invalid // FIXME: we should restore the current document's true URL instead of // setting it to nothing otherwise anything that depends on the URL // being correct will not work (i.e. the document will be called // "Untitled" which may not be true) // // Update: now the URL is restored in KisMainWindow but really, this // should still be fixed in KisDocument/KParts (ditto for file). // We still resetURL() here since we may or may not have been called // by KisMainWindow - Clarence resetURL(); // As we did not save, restore the "was modified" status setModified(wasModified); } emit sigSavingFinished(); clearFileProgressUpdater(); unlockAfterSaving(); return ret; } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } void KisDocument::setOutputMimeType(const QByteArray & mimeType) { d->outputMimeType = mimeType; } QByteArray KisDocument::outputMimeType() const { return d->outputMimeType; } bool KisDocument::fileBatchMode() const { return d->importExportManager->batchMode(); } void KisDocument::setFileBatchMode(const bool batchMode) { d->importExportManager->setBatchMode(batchMode); } bool KisDocument::isImporting() const { return d->isImporting; } bool KisDocument::isExporting() const { return d->isExporting; } void KisDocument::slotAutoSave() { //qDebug() << "slotAutoSave. Modified:" << d->modified << "modifiedAfterAutosave" << d->modified << "url" << url() << localFilePath(); if (!d->isAutosaving && d->modified && d->modifiedAfterAutosave) { connect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int))); emit statusBarMessage(i18n("Autosaving...")); d->isAutosaving = true; QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); QByteArray mimetype = d->outputMimeType; d->outputMimeType = nativeFormatMimeType(); bool ret = exportDocument(QUrl::fromLocalFile(autoSaveFileName)); d->outputMimeType = mimetype; if (ret) { d->modifiedAfterAutosave = false; d->autoSaveTimer.stop(); // until the next change } d->isAutosaving = false; emit clearStatusBarMessage(); disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int))); if (!ret && !d->disregardAutosaveFailure) { emit statusBarMessage(i18n("Error during autosave! Partition full?")); } } } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setAutoSaveDelay(d->autoSaveDelay); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { //qDebug() << "setting autosave delay from" << d->autoSaveDelay << "to" << delay; d->autoSaveDelay = delay; if (isReadWrite() && d->autoSaveDelay > 0) { d->autoSaveTimer.start(d->autoSaveDelay * 1000); } else { d->autoSaveTimer.stop(); } } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); if (path.isEmpty()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #endif } else { QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); d->isImporting = true; // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } d->isImporting = false; return ret; } bool KisDocument::openUrl(const QUrl &_url, KisDocument::OpenUrlFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open it instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened) { resetURL(); // Force save to act like 'Save As' setReadWrite(true); // enable save button setModified(true); } else { if( !(flags & OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES) ) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } if (ret) { // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } } return ret; } +class DlgLoadMessages : public KoDialog { +public: + DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { + setWindowTitle(title); + setWindowIcon(KisIconUtils::loadIcon("dialog-warning")); + QWidget *page = new QWidget(this); + QVBoxLayout *layout = new QVBoxLayout(page); + QHBoxLayout *hlayout = new QHBoxLayout(); + QLabel *labelWarning= new QLabel(); + labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-warning").pixmap(32, 32)); + hlayout->addWidget(labelWarning); + hlayout->addWidget(new QLabel(message)); + layout->addLayout(hlayout); + QTextBrowser *browser = new QTextBrowser(); + QString warning = "

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

"; + } + else { + warning += " Reasons:

"; + } + warning += "

    "; + + Q_FOREACH(const QString &w, warnings) { + warning += "\n
  • " + w + "
  • "; + } + warning += "
"; + browser->setHtml(warning); + browser->setMinimumHeight(200); + browser->setMinimumWidth(400); + layout->addWidget(browser); + setMainWidget(page); + setButtons(KoDialog::Ok); + resize(minimumSize()); + } +}; + bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; setFileProgressUpdater(i18n("Opening Document")); KisImportExportFilter::ConversionStatus status; status = d->importExportManager->importDocument(localFilePath(), typeName); if (status != KisImportExportFilter::OK) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) { - QString errorMsg(i18n("Could not open %2.\nReason: %1.\n%3", msg, prettyPathOrUrl(), errorMessage())); - QMessageBox::critical(0, i18nc("@title:window", "Krita"), errorMsg); + DlgLoadMessages dlg(i18nc("@title:window", "Krita"), + i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), + errorMessage().split("\n") + warningMessage().split("\n")); + dlg.exec(); } clearFileProgressUpdater(); return false; } + else if (!warningMessage().isEmpty()) { + DlgLoadMessages dlg(i18nc("@title:window", "Krita"), + i18n("There were problems opening %1.", prettyPathOrUrl()), + warningMessage().split("\n")); + dlg.exec(); + setUrl(QUrl()); + } setMimeTypeAfterLoading(typeName); emit sigLoadingFinished(); if (!d->suppressProgress && d->progressUpdater) { QPointer updater = d->progressUpdater->startSubtask(1, "clear undo stack"); updater->setProgress(0); undoStack()->clear(); updater->setProgress(100); clearFileProgressUpdater(); } else { undoStack()->clear(); } return true; } KoProgressUpdater *KisDocument::progressUpdater() const { return d->progressUpdater; } void KisDocument::setProgressProxy(KoProgressProxy *progressProxy) { d->progressProxy = progressProxy; } KoProgressProxy* KisDocument::progressProxy() const { if (!d->progressProxy) { KisMainWindow *mainWindow = 0; if (KisPart::instance()->mainwindowCount() > 0) { mainWindow = KisPart::instance()->mainWindows()[0]; } d->progressProxy = new DocumentProgressProxy(mainWindow); } return d->progressProxy; } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified() { d->modified = true; } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; if (documentInfo()) { c = documentInfo()->aboutInfo("title"); } const QString _url(url().fileName()); if (!c.isEmpty() && !_url.isEmpty()) { c = QString("%1 - %2").arg(c).arg(_url); } else if (c.isEmpty()) { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } +void KisDocument::setWarningMessage(const QString& warningMsg) +{ + d->lastWarningMessage = warningMsg; +} + +QString KisDocument::warningMessage() const +{ + return d->lastWarningMessage; +} + + void KisDocument::removeAutoSaveFiles() { //qDebug() << "removeAutoSaveFiles"; // Eliminate any auto-save file QString asf = generateAutoSaveFileName(localFilePath()); // the one in the current dir //qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf); if (QFile::exists(asf)) { //qDebug() << "\tremoving autosavefile" << asf; QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME //qDebug() << "Autsavefile in $home" << asf; if (QFile::exists(asf)) { //qDebug() << "\tremoving autsavefile 2" << asf; QFile::remove(asf); } } void KisDocument::setBackupFile(bool saveBackup) { d->backupFile = saveBackup; } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackIndexChanged(int idx) { // even if the document was already modified, call setModified to re-start autosave timer setModified(idx != d->undoStack->cleanIndex()); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { d->gridConfig = config; } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) return false; if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) return false; d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setFileProgressProxy(); setUrl(d->m_url); ret = openFile(); clearFileProgressProxy(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisConfig cfg; KisImageSP image; KisPaintLayerSP layer; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); documentInfo()->setAboutInfo("title", name); if (name != i18n("Unnamed") && !name.isEmpty()) { setUrl(QUrl::fromLocalFile(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation) + '/' + name + ".kra")); } documentInfo()->setAboutInfo("abstract", description); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); Q_CHECK_PTR(layer); if (backgroundAsLayer) { image->setDefaultProjectionColor(KoColor(cs)); if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) { layer->paintDevice()->setDefaultPixel(bgColor); } else { // Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel KisFillPainter painter; painter.begin(layer->paintDevice()); painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8()); } } else { image->setDefaultProjectionColor(bgColor); } layer->setDirty(QRect(0, 0, width, height)); image->addNode(layer.data(), image->rootLayer().data()); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); QApplication::restoreOverrideCursor(); return true; } KoShapeBasedDocumentBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } vKisNodeSP KisDocument::activeNodes() const { vKisNodeSP nodes; Q_FOREACH (KisView *v, KisPart::instance()->views()) { if (v->document() == this && v->viewManager()) { KisNodeSP activeNode = v->viewManager()->activeNode(); if (activeNode && !nodes.contains(activeNode)) { if (activeNode->inherits("KisMask")) { activeNode = activeNode->parent(); } nodes.append(activeNode); } } } return nodes; } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList value) { d->assistants = value; } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } void KisDocument::setFileProgressUpdater(const QString &text) { d->suppressProgress = d->importExportManager->batchMode(); if (!d->suppressProgress) { d->progressUpdater = new KoProgressUpdater(d->progressProxy, KoProgressUpdater::Unthreaded); d->progressUpdater->start(100, text); d->importExportManager->setProgresUpdater(d->progressUpdater); if (KisPart::instance()->currentMainwindow()) { connect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int))); connect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled())); } } } void KisDocument::clearFileProgressUpdater() { if (!d->suppressProgress && d->progressUpdater) { if (KisPart::instance()->currentMainwindow()) { disconnect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled())); disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int))); } delete d->progressUpdater; d->importExportManager->setProgresUpdater(0); d->progressUpdater = 0; } } void KisDocument::setFileProgressProxy() { if (!d->progressProxy && !d->importExportManager->batchMode()) { d->fileProgressProxy = progressProxy(); } else { d->fileProgressProxy = 0; } } void KisDocument::clearFileProgressProxy() { if (d->fileProgressProxy) { setProgressProxy(0); delete d->fileProgressProxy; d->fileProgressProxy = 0; } } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image) { if (d->image) { // Disconnect existing sig/slot connections d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); d->image->initialRefreshGraph(); setAutoSaveDelay(KisConfig().autoSaveInterval()); } void KisDocument::setImageModified() { setModified(true); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } bool KisDocument::prepareLocksForSaving() { KisImageSP copiedImage; // XXX: Restore this when // a) cloning works correctly and // b) doesn't take ages because it needs to refresh its entire graph and finally, // c) we do use the saving image to save in the background. { Private::SafeSavingLocker locker(d, this); if (locker.successfullyLocked()) { copiedImage = d->image; //->clone(true); } else if (!isAutosaving()) { // even though it is a recovery operation, we should ensure we do not enter saving twice! std::unique_lock> l(d->savingLock, std::try_to_lock); if (l.owns_lock()) { d->lastErrorMessage = i18n("The image was still busy while saving. Your saved image might be incomplete."); d->image->lock(); copiedImage = d->image; //->clone(true); //copiedImage->initialRefreshGraph(); d->image->unlock(); } } } bool result = false; // ensure we do not enter saving twice if (copiedImage && d->savingMutex.tryLock()) { d->savingImage = copiedImage; result = true; } else { qWarning() << "Could not lock the document for saving!"; d->lastErrorMessage = i18n("Could not lock the image for saving."); } return result; } void KisDocument::unlockAfterSaving() { d->savingImage = 0; d->savingMutex.unlock(); } diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h index 2a7aa6cd8d..d20eba1898 100644 --- a/libs/ui/KisDocument.h +++ b/libs/ui/KisDocument.h @@ -1,607 +1,620 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISDOCUMENT_H #define KISDOCUMENT_H #include #include #include #include #include #include #include #include #include #include #include #include "kritaui_export.h" class QString; class KUndo2Command; class KoUnit; class KoColor; class KoColorSpace; class KoShapeBasedDocumentBase; class KoShapeLayer; class KoStore; class KoOdfReadStore; class KoDocumentInfo; class KoProgressUpdater; class KoProgressProxy; class KoDocumentInfoDlg; class KisImportExportManager; class KisUndoStore; class KisPaintingAssistant; class KisPart; class KisGridConfig; class KisGuidesConfig; class QDomDocument; class KisPart; #define KIS_MIME_TYPE "application/x-krita" /** * The %Calligra document class * * This class provides some functionality each %Calligra document should have. * * @short The %Calligra document class */ class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase { Q_OBJECT protected: explicit KisDocument(); public: enum OpenUrlFlags { OPEN_URL_FLAG_NONE = 1 << 0, OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES = 1 << 1, }; /** * Destructor. * * The destructor does not delete any attached KisView objects and it does not * delete the attached widget as returned by widget(). */ virtual ~KisDocument(); /** * @brief reload Reloads the document from the original url * @return the result of loading the document */ bool reload(); /** * @brief openUrl Open an URL * @param url The URL to open * @param flags Control specific behavior * @return success status */ bool openUrl(const QUrl &url, OpenUrlFlags flags = OPEN_URL_FLAG_NONE); /** * Opens the document given by @p url, without storing the URL * in the KisDocument. * Call this instead of openUrl() to implement KisMainWindow's * File --> Import feature. * * @note This will call openUrl(). To differentiate this from an ordinary * Open operation (in any reimplementation of openUrl() or openFile()) * call isImporting(). */ bool importDocument(const QUrl &url); /** * Saves the document as @p url without changing the state of the * KisDocument (URL, modified flag etc.). Call this instead of * KisParts::ReadWritePart::saveAs() to implement KisMainWindow's * File --> Export feature. * * @note This will call KisDocument::saveAs(). To differentiate this * from an ordinary Save operation (in any reimplementation of * saveFile()) call isExporting(). */ bool exportDocument(const QUrl &url, KisPropertiesConfigurationSP exportConfiguration = 0); /** * @brief Sets whether the document can be edited or is read only. * * This recursively applied to all child documents and * KisView::updateReadWrite is called for every attached * view. */ void setReadWrite(bool readwrite = true); /** * To be preferred when a document exists. It is fast when calling * it multiple times since it caches the result that readNativeFormatMimeType() * delivers. * This comes from the X-KDE-NativeMimeType key in the .desktop file. */ static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; } /// Checks whether a given mimetype can be handled natively. bool isNativeFormat(const QByteArray& mimetype) const; /// Returns a list of the mimetypes considered "native", i.e. which can /// be saved by KisDocument without a filter, in *addition* to the main one static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; } /** * Returns the actual mimetype of the document */ QByteArray mimeType() const; /** * @brief Sets the mime type for the document. * * When choosing "save as" this is also the mime type * selected by default. */ void setMimeType(const QByteArray & mimeType); /** * @brief Set the format in which the document should be saved. * * This is called on loading, and in "save as", so you shouldn't * have to call it. * * @param mimeType the mime type (format) to use. */ void setOutputMimeType(const QByteArray & mimeType); QByteArray outputMimeType() const; /** * @return true if file operations should inhibit the option dialog */ bool fileBatchMode() const; /** * @param batchMode if true, do not show the option dialog for file operations. */ void setFileBatchMode(const bool batchMode); /** * Sets the error message to be shown to the user (use i18n()!) * when loading or saving fails. * If you asked the user about something and they chose "Cancel", */ void setErrorMessage(const QString& errMsg); /** * Return the last error message. Usually KisDocument takes care of * showing it; this method is mostly provided for non-interactive use. */ QString errorMessage() const; + /** + * Sets the warning message to be shown to the user (use i18n()!) + * when loading or saving fails. + */ + void setWarningMessage(const QString& warningMsg); + + /** + * Return the last warning message set by loading or saving. Warnings + * mean that the document could not be completely loaded, but the errors + * were not absolutely fatal. + */ + QString warningMessage() const; + /** * @brief Generates a preview picture of the document * @note The preview is used in the File Dialog and also to create the Thumbnail */ QPixmap generatePreview(const QSize& size); /** * Tells the document that its title has been modified, either because * the modified status changes (this is done by setModified() ) or * because the URL or the document-info's title changed. */ void setTitleModified(); /** * @brief Sets the document to empty. * * Used after loading a template * (which is not empty, but not the user's input). * * @see isEmpty() */ void setEmpty(bool empty = true); /** * Return a correctly created QDomDocument for this KisDocument, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * @param tagName the name of the tag for the root element * @param version the DTD version (usually the application's version). */ QDomDocument createDomDocument(const QString& tagName, const QString& version) const; /** * Return a correctly created QDomDocument for an old (1.3-style) %Calligra document, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * This static method can be used e.g. by filters. * @param appName the app's instance name, e.g. words, kspread, kpresenter etc. * @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter. * @param version the DTD version (usually the application's version). */ static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version); /** * Loads a document in the native format from a given URL. * Reimplement if your native format isn't XML. * * @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter */ bool loadNativeFormat(const QString & file); /** * Activate/deactivate/configure the autosave feature. * @param delay in seconds, 0 to disable */ void setAutoSaveDelay(int delay); /** * @return the information concerning this document. * @see KoDocumentInfo */ KoDocumentInfo *documentInfo() const; /** * @return the object to report progress to. * * This is only not zero if loading or saving is in progress. * * One can add more KoUpdaters to it to make the progress reporting more * accurate. If no active progress reporter is present, 0 is returned. **/ KoProgressUpdater *progressUpdater() const; /** * Set a custom progress proxy to use to report loading * progress to. */ void setProgressProxy(KoProgressProxy *progressProxy); KoProgressProxy* progressProxy() const; /** * Performs a cleanup of unneeded backup files */ void removeAutoSaveFiles(); /** * @brief setBackupFile enable/disable saving a backup of the file on saving * @param saveBackup if true, Krita will save a backup of the file */ void setBackupFile(bool saveBackup); /** * Returns true if this document or any of its internal child documents are modified. */ bool isModified() const; /** * @return caption of the document * * Caption is of the form "[title] - [url]", * built out of the document info (title) and pretty-printed * document URL. * If the title is not present, only the URL it returned. */ QString caption() const; /** * Sets the document URL to empty URL * KParts doesn't allow this, but %Calligra apps have e.g. templates * After using loadNativeFormat on a template, one wants * to set the url to QUrl() */ void resetURL(); /** * @internal (public for KisMainWindow) */ void setMimeTypeAfterLoading(const QString& mimeType); /** * Returns the unit used to display all measures/distances. */ KoUnit unit() const; /** * Sets the unit used to display all measures/distances. */ void setUnit(const KoUnit &unit); KisGridConfig gridConfig() const; void setGridConfig(const KisGridConfig &config); /// returns the guides data for this document. const KisGuidesConfig& guidesConfig() const; void setGuidesConfig(const KisGuidesConfig &data); void clearUndoHistory(); /** * Sets the modified flag on the document. This means that it has * to be saved or not before deleting it. */ void setModified(bool _mod); void updateEditingTime(bool forceStoreElapsed); /** * Returns the global undo stack */ KUndo2Stack *undoStack(); /** * @brief importExportManager gives access to the internal import/export manager * @return the document's import/export manager */ KisImportExportManager *importExportManager() const; public Q_SLOTS: /** * Adds a command to the undo stack and executes it by calling the redo() function. * @param command command to add to the undo stack */ void addCommand(KUndo2Command *command); /** * Begins recording of a macro command. At the end endMacro needs to be called. * @param text command description */ void beginMacro(const KUndo2MagicString &text); /** * Ends the recording of a macro command. */ void endMacro(); Q_SIGNALS: /** * This signal is emitted when the unit is changed by setUnit(). * It is common to connect views to it, in order to change the displayed units * (e.g. in the rulers) */ void unitChanged(const KoUnit &unit); /** * Progress info while loading or saving. The value is in percents (i.e. a number between 0 and 100) * Your KisDocument-derived class should emit the signal now and then during load/save. * KisMainWindow will take care of displaying a progress bar automatically. */ void sigProgress(int value); /** * Progress cancel button pressed * This is emitted by KisDocument */ void sigProgressCanceled(); /** * Emitted e.g. at the beginning of a save operation * This is emitted by KisDocument and used by KisView to display a statusbar message */ void statusBarMessage(const QString& text); /** * Emitted e.g. at the end of a save operation * This is emitted by KisDocument and used by KisView to clear the statusbar message */ void clearStatusBarMessage(); /** * Emitted when the document is modified */ void modified(bool); void titleModified(const QString &caption, bool isModified); void sigLoadingFinished(); void sigSavingFinished(); void sigGuidesConfigChanged(const KisGuidesConfig &config); private: friend class KisPart; friend class SafeSavingLocker; /** * Generate a name for the document. */ QString newObjectName(); QString generateAutoSaveFileName(const QString & path) const; /** * Loads a document * * Applies a filter if necessary, and calls loadNativeFormat in any case * You should not have to reimplement, except for very special cases. * * NOTE: this method also creates a new KisView instance! * * This method is called from the KReadOnlyPart::openUrl method. */ bool openFile(); /** * Saves a document * * Applies a filter if necessary, and calls exportDocument in any case * You should not have to reimplement, except for very special cases. */ bool saveFile(const QString &filePath, KisPropertiesConfigurationSP exportConfiguration = 0); /** @internal */ void setModified(); /** * Returns whether or not the current openUrl() or openFile() call is * actually an import operation (like File --> Import). * This is for informational purposes only. */ bool isImporting() const; /** * Returns whether or not the current saveFile() call is actually an export * operation (like File --> Export). * If this function returns true during saveFile() and you are changing * some sort of state, you _must_ restore it before the end of saveFile(); * otherwise, File --> Export will not work properly. */ bool isExporting() const; public: bool isAutosaving() const; public: QString localFilePath() const; void setLocalFilePath( const QString &localFilePath ); KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const; bool isReadWrite() const; QUrl url() const; void setUrl(const QUrl &url); bool closeUrl(bool promptToSave = true); bool saveAs(const QUrl &url, KisPropertiesConfigurationSP exportConfigration = 0); /** * Create a new image that has this document as a parent and * replace the current image with this image. */ bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &imageDescription, const double imageResolution); KisImageWSP image() const; /** * @brief savingImage provides a detached, shallow copy of the original image that must be used when saving. * Any strokes in progress will not be applied to this image, so the result might be missing some data. On * the other hand, it won't block. * * @return a shallow copy of the original image, or 0 is saving is not in progress */ KisImageSP savingImage() const; /** * Adds progressproxy for file operations */ void setFileProgressProxy(); /** * Clears progressproxy for file operations */ void clearFileProgressProxy(); /** * Adds progressupdater for file operations */ void setFileProgressUpdater(const QString &text); /** * Clears progressupdater for file operations */ void clearFileProgressUpdater(); /** * Set the current image to the specified image and turn undo on. */ void setCurrentImage(KisImageSP image); KisUndoStore* createUndoStore(); /** * The shape controller matches internal krita image layers with * the flake shape hierarchy. */ KoShapeBasedDocumentBase * shapeController() const; KoShapeLayer* shapeForNode(KisNodeSP layer) const; /** * @return a list of all layers that are active in all current views */ vKisNodeSP activeNodes() const; /** * set the list of nodes that were marked as currently active */ void setPreActivatedNode(KisNodeSP activatedNode); /** * @return the node that was set as active during loading */ KisNodeSP preActivatedNode() const; QList assistants() const; void setAssistants(const QList value); bool save(KisPropertiesConfigurationSP exportConfiguration = 0); Q_SIGNALS: void completed(); void canceled(const QString &); private Q_SLOTS: void setImageModified(); void slotAutoSave(); /// Called by the undo stack when undo or redo is called void slotUndoStackIndexChanged(int idx); private: bool prepareLocksForSaving(); void unlockAfterSaving(); QString prettyPathOrUrl() const; bool openUrlInternal(const QUrl &url); class Private; Private *const d; }; Q_DECLARE_METATYPE(KisDocument*) #endif diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index bfbab36822..b2ff17e89d 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2403 +1,2404 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KIO #include #endif #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDockWidgetTitleBar.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include #include #include #include #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "KisDocument.h" #include "KisDocument.h" #include "kis_group_layer.h" #include "kis_icon_utils.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "kis_resource_server_provider.h" #include "kis_signal_compressor_with_param.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include "kis_animation_exporter.h" #include #ifdef Q_OS_WIN #include #endif class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); dockWidget->setTabEnabled(false); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent) : q(parent) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; KisViewManager *viewManager {0}; QPointer activeView; QPointer progress; QPointer progressCancel; QMutex progressMutex; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; bool isImporting {false}; bool isExporting {false}; bool noCleanup {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; QUrl lastExportUrl; QMap dockWidgetsMap; QMap dockWidgetVisibilityMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow() : KXmlGuiWindow() , d(new Private(this)) { KisConfig cfg; d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); setAcceptDrops(true); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_OSX setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*, QList >)), this, SLOT(newOptionWidgets(KoCanvasController*, QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setMainWindow(d->viewManager); } } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); setCentralWidget(d->mdiArea); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); createActions(); setAutoSaveSettings("krita", false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita.xmlgui")); setXMLFile(":/kxmlgui5/krita.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } plugActionList("toolbarlist", toolbarList); setToolbarList(toolbarList); applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); #ifdef Q_OS_WIN auto w = qApp->activeWindow(); if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true); #endif QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } } void KisMainWindow::setNoCleanup(bool noCleanup) { d->noCleanup = noCleanup; } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // dbgKrita << "", "").replace("", "") // << "iconText=" << action->iconText().replace("&", "&") // << "shortcut=" << action->shortcut(QAction::ActiveShortcut).toString() // << "defaultShortcut=" << action->shortcut(QAction::DefaultShortcut).toString() // << "isCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "statusTip=" << action->statusTip() // << "/>" ; // } // else { // dbgKrita << "Got a QAction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); if (d->noCleanup) return; delete d->viewManager; delete d; } void KisMainWindow::addView(KisView *view) { if (d->activeView == view) return; if (d->activeView) { d->activeView->disconnect(this); } showView(view); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified(QString,bool))); } } void KisMainWindow::showView(KisView *imageView) { if (imageView && activeView() != imageView ) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg; subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } emit themeChanged(); } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { dbgUI << "KisMainWindow::addRecentURL url=" << url.toDisplayString(); // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) if (path.contains(*it)) ok = false; // it's in the tmp resource #ifdef HAVE_KIO if (ok) { KRecentDocument::add(QUrl::fromLocalFile(path)); } #endif } #ifdef HAVE_KIO else { KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash)); } #endif if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KMainWindow* window, KMainWindow::memberList()) static_cast(window)->reloadRecentFileList(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries( KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document()){ QString caption( d->activeView->document()->caption() ); if (d->readOnly) { caption += ' ' + i18n("(write protected)"); } d->activeView->setWindowTitle(caption); updateCaption(caption, d->activeView->document()->isModified()); if (!d->activeView->document()->url().fileName().isEmpty()) d->saveAction->setToolTip(i18n("Save as %1", d->activeView->document()->url().fileName())); else d->saveAction->setToolTip(i18n("Save")); } } void KisMainWindow::updateCaption(const QString & caption, bool mod) { dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")"; #ifdef KRITA_ALPHA setCaption(QString("ALPHA %1: %2").arg(KRITA_ALPHA).arg(caption), mod); return; #endif #ifdef KRITA_BETA setCaption(QString("BETA %1: %2").arg(KRITA_BETA).arg(caption), mod); return; #endif #ifdef KRITA_RC setCaption(QString("RELEASE CANDIDATE %1: %2").arg(KRITA_RC).arg(caption), mod); return; #endif setCaption(caption, mod); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url) { if (!QFile(url.toLocalFile()).exists()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url); } bool KisMainWindow::openDocumentInternal(const QUrl &url, KisDocument *newdoc) { if (!url.isLocalFile()) { qDebug() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } if (!newdoc) { newdoc = KisPart::instance()->createDocument(); } d->firstTime = true; connect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); bool openRet = (!d->isImporting) ? newdoc->openUrl(url) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } void KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this); addView(view); emit guiLoadingFinished(); } QStringList KisMainWindow::showOpenFileDialog() { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import)); dialog.setCaption(d->isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() != QDialog::Accepted) { return false; } bool reset_url; if (document->url().isEmpty()) { reset_url = true; saveas = true; } else { reset_url = false; } connect(document, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); QUrl oldURL = document->url(); QString oldFile = document->localFilePath(); QByteArray _native_format = document->nativeFormatMimeType(); QByteArray oldOutputFormat = document->outputMimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter; mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export); if (!mimeFilter.contains(oldOutputFormat) && !d->isExporting) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldOutputFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = suggestedURL.fileName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name int c = suggestedFilename.lastIndexOf('.'); const QString ext = KisMimeDatabase::suffixesForMimeType(_native_format).first(); if (!ext.isEmpty()) { if (c < 0) suggestedFilename = suggestedFilename + "." + ext; else suggestedFilename = suggestedFilename.left(c) + "." + ext; } else { // current filename extension wrong anyway if (c > 0) { // this assumes that a . signifies an extension, not just a . suggestedFilename = suggestedFilename.left(c); } } suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(i18n("untitled")); if (d->isExporting && !d->lastExportUrl.isEmpty()) { dialog.setDefaultDir(d->lastExportUrl.toLocalFile()); } else { dialog.setDefaultDir(suggestedURL.toLocalFile()); } // Default to all supported file types if user is exporting, otherwise use Krita default dialog.setMimeTypeFilters(mimeFilter, QString(_native_format)); QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(_native_format).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.baseName()); } QByteArray outputFormat = _native_format; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile()); outputFormat = outputFormatString.toLatin1(); if (!d->isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { justChangingFilterOptions = (newURL == d->lastExportUrl) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { // // Note: // If the user is stupid enough to Export to the current URL, // we do _not_ change this operation into a Save As. Reasons // follow: // // 1. A check like "d->isExporting && oldURL == newURL" // doesn't _always_ work on case-insensitive filesystems // and inconsistent behaviour is bad. // 2. It is probably not a good idea to change document->mimeType // and friends because the next time the user File/Save's, // (not Save As) they won't be expecting that they are // using their File/Export settings // // As a bad side-effect of this, the modified flag will not // be updated and it is possible that what is currently on // their screen is not what is stored on disk (through loss // of formatting). But if you are dumb enough to change // mimetype but not the filename, then arguably, _you_ are // the "bug" :) // // - Clarence // document->setOutputMimeType(outputFormat); if (!d->isExporting) { // Save As ret = document->saveAs(newURL); if (ret) { dbgUI << "Successful Save As!"; addRecentURL(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); document->setOutputMimeType(oldOutputFormat); } } else { // Export ret = document->exportDocument(newURL); if (ret) { // a few file dialog convenience things d->lastExportUrl = newURL; d->lastExportedFormat = outputFormat; } // always restore output format document->setOutputMimeType(oldOutputFormat); } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // be sure document has the correct outputMimeType! if (d->isExporting || document->isModified()) { ret = document->save(); } if (!ret) { dbgUI << "Failed Save!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); } } if (!ret && reset_url) document->resetURL(); //clean the suggested filename as the save dialog was rejected updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->undoAction()->trigger(); d->undo->setText(activeView()->undoAction()->text()); } } void KisMainWindow::redo() { if (activeView()) { activeView()->redoAction()->trigger(); d->redo->setText(activeView()->redoAction()->text()); } } void KisMainWindow::closeEvent(QCloseEvent *e) { d->mdiArea->closeAllSubWindows(); QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); cfg.writeEntry("ko_geometry", saveGeometry().toBase64()); cfg.writeEntry("ko_windowstate", saveState().toBase64()); { KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); } QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); if (d->noCleanup) return; Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { KisView *view = dynamic_cast(subwin); if (view) { KisPart::instance()->removeView(view); } } if (!d->dockWidgetVisibilityMap.isEmpty()) { // re-enable dockers for persistency Q_FOREACH (QDockWidget* dockWidget, d->dockWidgetsMap) dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget)); } } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = config->group("MainWindow"); KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); // Save collapsable state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); actionCollection()->action("edit_undo")->setText(activeView()->undoAction()->text()); actionCollection()->action("edit_redo")->setText(activeView()->redoAction()->text()); d->viewManager->setCurrentView(view); } void KisMainWindow::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisMainWindow::dropEvent(QDropEvent *event) { if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) { Q_FOREACH (const QUrl &url, event->mimeData()->urls()) { openDocument(url); } } } void KisMainWindow::dragMoveEvent(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeaveEvent(QDragLeaveEvent * /*event*/) { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); KisConfig cfg; int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(const QUrl&)), KisPart::instance(), SLOT(openTemplate(const QUrl&))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotFileOpen() { QStringList urls = showOpenFileDialog(); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { bool res = openDocument(QUrl::fromLocalFile(url)); if (!res) { warnKrita << "Loading" << url << "failed"; } } } } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile())); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document())) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true)) { emit documentSaved(); } } KoCanvasResourceManager *KisMainWindow::resourceManager() const { return d->viewManager->resourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } bool KisMainWindow::restoreWorkspace(const QByteArray &state) { QByteArray oldState = saveState(); const bool showTitlebars = KisConfig().showDockerTitleBars(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->hide(); dock->titleBarWidget()->setVisible(showTitlebars); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { dock->titleBarWidget()->setVisible(showTitlebars || dock->isFloating()); } } return false; } Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget(); dock->titleBarWidget()->setVisible(showTitlebars || (dock->isFloating() && isCollapsed)); } } return success; } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { if(!slotFileCloseAll()) return; close(); Q_FOREACH (QPointer mainWin, KisPart::instance()->mainWindows()) { if (mainWin != this) { if(!mainWin->slotFileCloseAll()) return; mainWin->close(); } } } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); document->setFileProgressProxy(); document->setFileProgressUpdater(i18n("Import frames")); KisAnimationImporter importer(document); KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); document->clearFileProgressUpdater(); document->clearFileProgressProxy(); if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(KSharedConfig::openConfig()->group("krita")); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg; cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } void KisMainWindow::slotProgress(int value) { qApp->processEvents(); StdLockableWrapper wrapper(&d->progressMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return; dbgUI << "KisMainWindow::slotProgress" << value; if (value <= -1 || value >= 100) { if (d->progress) { statusBar()->removeWidget(d->progress); delete d->progress; d->progress = 0; disconnect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled())); statusBar()->removeWidget(d->progressCancel); delete d->progressCancel; d->progressCancel = 0; } d->firstTime = true; return; } if (d->firstTime || !d->progress) { // The statusbar might not even be created yet. // So check for that first, and create it if necessary QStatusBar *bar = findChild(); if (!bar) { statusBar()->show(); QApplication::sendPostedEvents(this, QEvent::ChildAdded); } if (d->progress) { statusBar()->removeWidget(d->progress); delete d->progress; d->progress = 0; disconnect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled())); statusBar()->removeWidget(d->progressCancel); delete d->progressCancel; d->progress = 0; } d->progressCancel = new QToolButton(statusBar()); d->progressCancel->setMaximumHeight(statusBar()->fontMetrics().height()); d->progressCancel->setIcon(KisIconUtils::loadIcon("process-stop")); statusBar()->addPermanentWidget(d->progressCancel); d->progress = new QProgressBar(statusBar()); d->progress->setMaximumHeight(statusBar()->fontMetrics().height()); d->progress->setRange(0, 100); statusBar()->addPermanentWidget(d->progress); connect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled())); d->progress->show(); d->progressCancel->show(); d->firstTime = false; } if (!d->progress.isNull()) { d->progress->setValue(value); } qApp->processEvents(); } void KisMainWindow::slotProgressCanceled() { emit sigProgressCanceled(); } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; d->isImporting = true; slotFileOpen(); d->isImporting = false; } void KisMainWindow::slotExportFile() { dbgUI << "slotExportFile()"; d->isExporting = true; slotFileSaveAs(); d->isExporting = false; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } KoDockWidgetTitleBar *titleBar = dynamic_cast(dockWidget->titleBarWidget()); // Check if the dock widget is supposed to be collapsable if (!dockWidget->titleBarWidget()) { titleBar = new KoDockWidgetTitleBar(dockWidget); dockWidget->setTitleBarWidget(titleBar); titleBar->setCollapsable(factory->isCollapsable()); } titleBar->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } bool collapsed = factory->defaultCollapsed(); bool locked = false; group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id()); collapsed = group.readEntry("Collapsed", collapsed); locked = group.readEntry("Locked", locked); //dbgKrita << "docker" << factory->id() << dockWidget << "collapsed" << collapsed << "locked" << locked << "titlebar" << titleBar; if (titleBar && collapsed) titleBar->setCollapsed(true); if (titleBar && locked) titleBar->setLocked(true); d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_OSX dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::setToolbarList(QList toolbarList) { qDeleteAll(d->toolbarList); d->toolbarList = toolbarList; } void KisMainWindow::slotDocumentTitleModified(const QString &caption, bool mod) { updateCaption(); updateCaption(caption, mod); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = doc->url().toDisplayString(); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, child->document()->url().toDisplayString()); } else { text = i18n("%1 %2", i + 1, child->document()->url().toDisplayString()); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { + d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg; QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QBrush brush(cfg.getMDIBackgroundColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } void KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); } void KisMainWindow::newWindow() { KisPart::instance()->createMainWindow()->show(); } void KisMainWindow::closeCurrentWindow() { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Calligra LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointerKisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_OSX w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { title = d->activeView->document()->url().fileName(); // strip off the native extension (I don't want foobar.kwd.ps when printing into a file) QString extension = KisMimeDatabase::suffixesForMimeType(d->activeView->document()->outputMimeType()).first(); if (title.endsWith(extension)) { title.chop(extension.length()); } } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); KisConfig cfg; actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo ->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); d->toggleDockers = actionManager->createAction("view_toggledockers"); cfg.showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); d->toggleDockerTitleBars = actionManager->createAction("view_toggledockertitlebars"); d->toggleDockerTitleBars->setChecked(cfg.showDockerTitleBars()); connect(d->toggleDockerTitleBars, SIGNAL(toggled(bool)), SLOT(showDockerTitleBars(bool))); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createAction("file_close"); connect(d->close, SIGNAL(triggered()), SLOT(closeCurrentWindow())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->availableGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) { desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum)); } quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to componensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } restoreWorkspace(QByteArray::fromBase64(cfg.readEntry("ko_windowstate", QByteArray()))); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::showDockerTitleBars(bool show) { Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget(); dock->titleBarWidget()->setVisible(show || (dock->isFloating() && isCollapsed)); } } KisConfig cfg; cfg.setShowDockerTitleBars(show); } void KisMainWindow::moveEvent(QMoveEvent *e) { if (qApp->desktop()->screenNumber(this) != qApp->desktop()->screenNumber(e->oldPos())) { KisConfigNotifier::instance()->notifyConfigChanged(); } } #include diff --git a/libs/ui/KisMainWindow.h b/libs/ui/KisMainWindow.h index 9e351ac3fc..e1ef965785 100644 --- a/libs/ui/KisMainWindow.h +++ b/libs/ui/KisMainWindow.h @@ -1,460 +1,459 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2004 David Faure 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_MAIN_WINDOW_H #define KIS_MAIN_WINDOW_H #include "kritaui_export.h" #include #include #include #include #include #include #include "KisView.h" class QCloseEvent; class QMoveEvent; struct KoPageLayout; class KoCanvasResourceManager; class KisDocument; class KisPrintJob; class KoDockFactoryBase; class QDockWidget; class KisView; class KisViewManager; class KoCanvasController; /** * @brief Main window for Krita * * This class is used to represent a main window within a Krita session. Each * main window contains a menubar and some toolbars, and potentially several * views of several canvases. * */ class KRITAUI_EXPORT KisMainWindow : public KXmlGuiWindow, public KoCanvasSupervisor { Q_OBJECT public: /** * Constructor. * * Initializes a Calligra main window (with its basic GUI etc.). */ explicit KisMainWindow(); /** * Destructor. */ virtual ~KisMainWindow(); /** * Update caption from document info - call when document info * (title in the about page) changes. */ void updateCaption(); // If noCleanup is set, KisMainWindow will not delete the root document // or part manager on destruction. void setNoCleanup(bool noCleanup); /** * @brief showView shows the given view. Override this if you want to show * the view in a different way than by making it the central widget, for instance * as an QMdiSubWindow */ virtual void showView(KisView *view); /** * @returns the currently active view */ KisView *activeView() const; /** * Sets the maximum number of recent documents entries. */ void setMaxRecentItems(uint _number); /** * The document opened a URL -> store into recent documents list. */ void addRecentURL(const QUrl &url); /** * Load the desired document and show it. * @param url the URL to open * * @return TRUE on success. */ bool openDocument(const QUrl &url); void setReadWrite(bool readwrite); /// Return the list of dock widgets belonging to this main window. QList dockWidgets() const; QDockWidget* dockWidget(const QString &id); QList canvasObservers() const; KoCanvasResourceManager *resourceManager() const; int viewCount() const; /** * A wrapper around restoreState * @param state the saved state * @return TRUE on success */ bool restoreWorkspace(const QByteArray &state); KisViewManager *viewManager() const; void addViewAndNotifyLoadingCompleted(KisDocument *document); QStringList showOpenFileDialog(); /** * Shows if the main window is saving anything right now. If the * user presses Ctrl+W too fast, then the document can be close * before the saving is completed. I'm not sure if it is fixable * in any way without avoiding using porcessEvents() * everywhere (DK) * * Don't use it unless you have no option. */ bool hackIsSaving() const; Q_SIGNALS: /** * This signal is emitted if the document has been saved successfully. */ void documentSaved(); /// This signal is emitted when this windows has finished loading of a /// document. The document may be opened in another window in the end. /// In this case, the signal means there is no link between the window /// and the document anymore. void loadCompleted(); /// This signal is emitted right after the docker states have been succefully restored from config void restoringDone(); /// This signal is emitted when the color theme changes void themeChanged(); /// This signal is emitted when the shortcut key configuration has changed void keyBindingsChanged(); void guiLoadingFinished(); /// This signal is emitted when the user clicked on the progressbar cancel void sigProgressCanceled(); public Q_SLOTS: /** * Slot for opening a new document. * * If the current document is empty, the new document replaces it. * If not, a new mainwindow will be opened for showing the document. */ void slotFileNew(); /** * Slot for opening a saved file. * * If the current document is empty, the opened document replaces it. * If not a new mainwindow will be opened for showing the opened file. */ void slotFileOpen(); /** * Slot for opening a file among the recently opened files. * * If the current document is empty, the opened document replaces it. * If not a new mainwindow will be opened for showing the opened file. */ void slotFileOpenRecent(const QUrl &); /** * @brief slotPreferences open the preferences dialog */ void slotPreferences(); /** * Saves the current document with the current name. */ void slotFileSave(); // XXX: disabled KisPrintJob* exportToPdf(QString pdfFileName = QString()); void slotProgress(int value); /** * Saves the document, asking for a filename if necessary. * * @param saveas if set to TRUE the user is always prompted for a filename * @param silent if set to TRUE rootDocument()->setTitleModified will not be called. * * @return TRUE on success, false on error or cancel * (don't display anything in this case, the error dialog box is also implemented here * but restore the original URL in slotFileSaveAs) */ bool saveDocument(KisDocument *document, bool saveas = false); /** * Update the option widgets to the argument ones, removing the currently set widgets. */ void newOptionWidgets(KoCanvasController *controller, const QList > & optionWidgetList); private Q_SLOTS: /** * Save the list of recent files. */ void saveRecentFiles(); void slotLoadCompleted(); void slotLoadCanceled(const QString &); void slotSaveCompleted(); void slotSaveCanceled(const QString &); void forceDockTabFonts(); void slotProgressCanceled(); /** * @internal */ void slotDocumentTitleModified(const QString &caption, bool mod); /** * Prints the actual document. */ void slotFilePrint(); /** * Saves the current document with a new name. */ void slotFileSaveAs(); void slotFilePrintPreview(); void importAnimation(); /** * Show a dialog with author and document information. */ void slotDocumentInfo(); /** * Closes all open documents. */ bool slotFileCloseAll(); /** * @brief showAboutApplication show the about box */ virtual void showAboutApplication(); /** * Closes the mainwindow. */ void slotFileQuit(); /** * Configure toolbars. */ void slotConfigureToolbars(); /** * Post toolbar config. * (Plug action lists back in, etc.) */ void slotNewToolbarConfig(); /** * Shows or hides a toolbar */ void slotToolbarToggled(bool toggle); /** * Toggle full screen on/off. */ void viewFullscreen(bool fullScreen); /** * Toggle docker titlebars on/off. */ void showDockerTitleBars(bool show); /** * Reload file */ void slotReloadFile(); /** * File --> Import * * This will call slotFileOpen(). */ void slotImportFile(); /** * File --> Export * * This will call slotFileSaveAs(). */ void slotExportFile(); /** * Hide the dockers */ void toggleDockersVisibility(bool visible); /** * Handle theme changes from theme manager */ void slotThemeChanged(); void undo(); void redo(); void updateWindowMenu(); void setActiveSubWindow(QWidget *window); void configChanged(); void newView(QObject *document); void newWindow(); void closeCurrentWindow(); void checkSanity(); /// Quits Krita with error message from m_errorMessage. void showErrorAndDie(); protected: void closeEvent(QCloseEvent * e); void resizeEvent(QResizeEvent * e); - // QWidget overrides virtual void dragEnterEvent(QDragEnterEvent * event); virtual void dropEvent(QDropEvent * event); virtual void dragMoveEvent(QDragMoveEvent * event); virtual void dragLeaveEvent(QDragLeaveEvent * event); void setToolbarList(QList toolbarList); public Q_SLOTS: /** * Add a the given view to the list of views of this mainwindow. * This is a private implementation. For public usage please use * newView() and addViewAndNotifyLoadingCompleted(). */ void addView(KisView *view); /// Set the active view, this will update the undo/redo actions void setActiveView(KisView *view); void subWindowActivated(); private: friend class KisApplication; /** * Returns the dockwidget specified by the @p factory. If the dock widget doesn't exist yet it's created. * Add a "view_palette_action_menu" action to your view menu if you want to use closable dock widgets. * @param factory the factory used to create the dock widget if needed * @return the dock widget specified by @p factory (may be 0) */ QDockWidget* createDockWidget(KoDockFactoryBase* factory); bool openDocumentInternal(const QUrl &url, KisDocument *newdoc = 0); /** * Reloads the recent documents list. */ void reloadRecentFileList(); /** * Updates the window caption based on the document info and path. */ void updateCaption(const QString & caption, bool mod); void updateReloadFileAction(KisDocument *doc); void saveWindowSettings(); QPointeractiveKisView(); void applyDefaultSettings(QPrinter &printer); void createActions(); void applyToolBarLayout(); protected: void moveEvent(QMoveEvent *e); private Q_SLOTS: void initializeGeometry(); void showManual(); void switchTab(int index); private: /** * Struct used in the list created by createCustomDocumentWidgets() */ struct CustomDocumentWidgetItem { /// Pointer to the custom document widget QWidget *widget; /// title used in the sidebar. If left empty it will be displayed as "Custom Document" QString title; /// icon used in the sidebar. If left empty it will use the unknown icon QString icon; }; class Private; Private * const d; QString m_errorMessage; bool m_dieOnError; }; #endif diff --git a/libs/ui/KisPaletteModel.cpp b/libs/ui/KisPaletteModel.cpp index d910b79435..fc3faca1f2 100644 --- a/libs/ui/KisPaletteModel.cpp +++ b/libs/ui/KisPaletteModel.cpp @@ -1,133 +1,133 @@ /* * 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 "KisPaletteModel.h" #include #include #include #include #include #include KisPaletteModel::KisPaletteModel(QObject* parent) : QAbstractTableModel(parent), m_colorSet(0), m_displayRenderer(KoDumbColorDisplayRenderer::instance()) { } KisPaletteModel::~KisPaletteModel() { } void KisPaletteModel::setDisplayRenderer(KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { if (m_displayRenderer) { disconnect(m_displayRenderer, 0, this, 0); } m_displayRenderer = displayRenderer; connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(slotDisplayConfigurationChanged())); } else { m_displayRenderer = KoDumbColorDisplayRenderer::instance(); } } void KisPaletteModel::slotDisplayConfigurationChanged() { reset(); } QVariant KisPaletteModel::data(const QModelIndex& index, int role) const { if (m_colorSet) { - int i = index.row()*columnCount()+index.column(); + quint32 i = (quint32)(index.row()*columnCount()+index.column()); if (i < m_colorSet->nColors()) { switch (role) { case Qt::DisplayRole: { - return m_colorSet->getColor(i).name; + return m_colorSet->getColorGlobal(i).name; } case Qt::BackgroundRole: { - QColor color = m_displayRenderer->toQColor(m_colorSet->getColor(i).color); + QColor color = m_displayRenderer->toQColor(m_colorSet->getColorGlobal(i).color); return QBrush(color); } } } } return QVariant(); } int KisPaletteModel::rowCount(const QModelIndex& /*parent*/) const { if (!m_colorSet) { return 0; } if (m_colorSet->columnCount() > 0) { return m_colorSet->nColors()/m_colorSet->columnCount() + 1; } return m_colorSet->nColors()/15 + 1; } int KisPaletteModel::columnCount(const QModelIndex& /*parent*/) const { if (m_colorSet && m_colorSet->columnCount() > 0) { return m_colorSet->columnCount(); } return 15; } Qt::ItemFlags KisPaletteModel::flags(const QModelIndex& /*index*/) const { Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; return flags; } QModelIndex KisPaletteModel::index(int row, int column, const QModelIndex& parent) const { int index = row*columnCount()+column; - if (m_colorSet && index < m_colorSet->nColors()) { + if (m_colorSet && (quint32)index < m_colorSet->nColors()) { return QAbstractTableModel::index(row, column, parent); } return QModelIndex(); } void KisPaletteModel::setColorSet(KoColorSet* colorSet) { m_colorSet = colorSet; reset(); } KoColorSet* KisPaletteModel::colorSet() const { return m_colorSet; } QModelIndex KisPaletteModel::indexFromId(int i) const { const int width = columnCount(); return width > 0 ? index(i / width, i & width) : QModelIndex(); } int KisPaletteModel::idFromIndex(const QModelIndex &index) const { return index.isValid() ? index.row() * columnCount() + index.column() : -1; } diff --git a/libs/ui/KisViewManager.cpp b/libs/ui/KisViewManager.cpp index acbbe0c0b4..549d303ca4 100644 --- a/libs/ui/KisViewManager.cpp +++ b/libs/ui/KisViewManager.cpp @@ -1,1307 +1,1307 @@ /* * This file is part of KimageShop^WKrayon^WKrita * * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 1999 Carsten Pfeiffer * 2002 Patrick Julien * 2003-2011 Boudewijn Rempt * 2004 Clarence Dang * 2011 José Luis Vergara * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "KisViewManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "input/kis_input_manager.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_canvas_controller.h" #include "canvas/kis_grid_manager.h" #include "dialogs/kis_dlg_blacklist_cleanup.h" #include "input/kis_input_profile_manager.h" #include "kis_action_manager.h" #include "kis_action.h" #include "kis_canvas_controls_manager.h" #include "kis_canvas_resource_provider.h" #include "kis_composite_progress_proxy.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_control_frame.h" #include "kis_coordinates_converter.h" #include "KisDocument.h" #include "kis_favorite_resource_manager.h" #include "kis_filter_manager.h" #include "kis_group_layer.h" #include #include "kis_image_manager.h" #include #include "kis_mainwindow_observer.h" #include "kis_mask_manager.h" #include "kis_mimedata.h" #include "kis_mirror_manager.h" #include "kis_node_commands_adapter.h" #include "kis_node.h" #include "kis_node_manager.h" #include "kis_painting_assistants_manager.h" #include #include "kis_paintop_box.h" #include #include "KisPart.h" #include "KisPrintJob.h" #include "kis_progress_widget.h" #include "kis_resource_server_provider.h" #include "kis_selection.h" #include "kis_selection_manager.h" #include "kis_shape_controller.h" #include "kis_shape_layer.h" #include #include "kis_statusbar.h" #include #include #include "kis_tooltip_manager.h" #include #include "KisView.h" #include "kis_zoom_manager.h" #include "widgets/kis_floating_message.h" #include "kis_signal_auto_connection.h" #include "kis_script_manager.h" #include "kis_icon_utils.h" #include "kis_guides_manager.h" #include "kis_derived_resources.h" #include "dialogs/kis_delayed_save_dialog.h" class BlockingUserInputEventFilter : public QObject { bool eventFilter(QObject *watched, QEvent *event) override { Q_UNUSED(watched); if(dynamic_cast(event) || dynamic_cast(event) || dynamic_cast(event)) { return true; } else { return false; } } }; class KisViewManager::KisViewManagerPrivate { public: KisViewManagerPrivate(KisViewManager *_q, KActionCollection *_actionCollection, QWidget *_q_parent) : filterManager(_q) , createTemplate(0) , saveIncremental(0) , saveIncrementalBackup(0) , openResourcesDirectory(0) , rotateCanvasRight(0) , rotateCanvasLeft(0) , resetCanvasRotation(0) , wrapAroundAction(0) , levelOfDetailAction(0) , showRulersAction(0) , rulersTrackMouseAction(0) , zoomTo100pct(0) , zoomIn(0) , zoomOut(0) , selectionManager(_q) , statusBar(_q) , controlFrame(_q, _q_parent) , nodeManager(_q) , imageManager(_q) , gridManager(_q) , canvasControlsManager(_q) , paintingAssistantsManager(_q) , actionManager(_q, _actionCollection) , mainWindow(0) , showFloatingMessage(true) , currentImageView(0) , canvasResourceProvider(_q) , canvasResourceManager() , guiUpdateCompressor(30, KisSignalCompressor::POSTPONE, _q) , actionCollection(_actionCollection) , mirrorManager(_q) , inputManager(_q) , scriptManager(_q) , actionAuthor(0) { canvasResourceManager.addDerivedResourceConverter(toQShared(new KisCompositeOpResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisEffectiveCompositeOpResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisOpacityResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisFlowResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisSizeResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisLodAvailabilityResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisEraserModeResourceConverter)); canvasResourceManager.addResourceUpdateMediator(toQShared(new KisPresetUpdateMediator)); } public: KisFilterManager filterManager; KisAction *createTemplate; KisAction *createCopy; KisAction *saveIncremental; KisAction *saveIncrementalBackup; KisAction *openResourcesDirectory; KisAction *rotateCanvasRight; KisAction *rotateCanvasLeft; KisAction *resetCanvasRotation; KisAction *wrapAroundAction; KisAction *levelOfDetailAction; KisAction *showRulersAction; KisAction *rulersTrackMouseAction; KisAction *zoomTo100pct; KisAction *zoomIn; KisAction *zoomOut; KisAction *softProof; KisAction *gamutCheck; KisSelectionManager selectionManager; KisGuidesManager guidesManager; KisStatusBar statusBar; KisControlFrame controlFrame; KisNodeManager nodeManager; KisImageManager imageManager; KisGridManager gridManager; KisCanvasControlsManager canvasControlsManager; KisPaintingAssistantsManager paintingAssistantsManager; BlockingUserInputEventFilter blockingEventFilter; KisActionManager actionManager; QMainWindow* mainWindow; QPointer savedFloatingMessage; bool showFloatingMessage; QPointer currentImageView; KisCanvasResourceProvider canvasResourceProvider; KoCanvasResourceManager canvasResourceManager; KisSignalCompressor guiUpdateCompressor; KActionCollection *actionCollection; KisMirrorManager mirrorManager; KisInputManager inputManager; KisSignalAutoConnectionsStore viewConnections; KisScriptManager scriptManager; KSelectAction *actionAuthor; // Select action for author profile. QByteArray canvasState; - bool blockUntillOperationsFinishedImpl(KisImageSP image, bool force); + bool blockUntilOperationsFinishedImpl(KisImageSP image, bool force); }; KisViewManager::KisViewManager(QWidget *parent, KActionCollection *_actionCollection) : d(new KisViewManagerPrivate(this, _actionCollection, parent)) { d->actionCollection = _actionCollection; d->mainWindow = dynamic_cast(parent); d->canvasResourceProvider.setResourceManager(&d->canvasResourceManager); connect(&d->guiUpdateCompressor, SIGNAL(timeout()), this, SLOT(guiUpdateTimeout())); createActions(); setupManagers(); // These initialization functions must wait until KisViewManager ctor is complete. d->statusBar.setup(); d->controlFrame.setup(parent); //Check to draw scrollbars after "Canvas only mode" toggle is created. this->showHideScrollbars(); QScopedPointer dummy(new KoDummyCanvasController(actionCollection())); KoToolManager::instance()->registerToolActions(actionCollection(), dummy.data()); QTimer::singleShot(0, this, SLOT(initializeStatusBarVisibility())); connect(KoToolManager::instance(), SIGNAL(inputDeviceChanged(KoInputDevice)), d->controlFrame.paintopBox(), SLOT(slotInputDeviceChanged(KoInputDevice))); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), d->controlFrame.paintopBox(), SLOT(slotToolChanged(KoCanvasController*,int))); connect(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), resourceProvider(), SLOT(slotNodeActivated(KisNodeSP))); connect(resourceProvider()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), d->controlFrame.paintopBox(), SLOT(slotCanvasResourceChanged(int,QVariant))); connect(KisPart::instance(), SIGNAL(sigViewAdded(KisView*)), SLOT(slotViewAdded(KisView*))); connect(KisPart::instance(), SIGNAL(sigViewRemoved(KisView*)), SLOT(slotViewRemoved(KisView*))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotUpdateAuthorProfileActions())); KisInputProfileManager::instance()->loadProfiles(); KisConfig cfg; d->showFloatingMessage = cfg.showCanvasMessages(); } KisViewManager::~KisViewManager() { KisConfig cfg; if (resourceProvider() && resourceProvider()->currentPreset()) { cfg.writeEntry("LastPreset", resourceProvider()->currentPreset()->name()); cfg.writeKoColor("LastForeGroundColor",resourceProvider()->fgColor()); cfg.writeKoColor("LastBackGroundColor",resourceProvider()->bgColor()); } cfg.writeEntry("baseLength", KoResourceItemChooserSync::instance()->baseLength()); delete d; } KActionCollection *KisViewManager::actionCollection() const { return d->actionCollection; } void KisViewManager::slotViewAdded(KisView *view) { d->inputManager.addTrackedCanvas(view->canvasBase()); if (viewCount() == 0) { d->statusBar.showAllStatusBarItems(); } } void KisViewManager::slotViewRemoved(KisView *view) { d->inputManager.removeTrackedCanvas(view->canvasBase()); if (viewCount() == 0) { d->statusBar.hideAllStatusBarItems(); } } void KisViewManager::setCurrentView(KisView *view) { bool first = true; if (d->currentImageView) { d->currentImageView->notifyCurrentStateChanged(false); d->currentImageView->canvasBase()->setCursor(QCursor(Qt::ArrowCursor)); first = false; KisDocument* doc = d->currentImageView->document(); if (doc) { doc->disconnect(this); } d->currentImageView->canvasController()->proxyObject->disconnect(&d->statusBar); d->viewConnections.clear(); } d->softProof->setChecked(view->softProofing()); d->gamutCheck->setChecked(view->gamutCheck()); QPointerimageView = qobject_cast(view); if (imageView) { // Wait for the async image to have loaded KisDocument* doc = view->document(); // connect(canvasController()->proxyObject, SIGNAL(documentMousePositionChanged(QPointF)), d->statusBar, SLOT(documentMousePositionChanged(QPointF))); d->currentImageView = imageView; // Restore the last used brush preset, color and background color. if (first) { KisConfig cfg; KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); QString lastPreset = cfg.readEntry("LastPreset", QString("Basic_tip_default")); KisPaintOpPresetSP preset = rserver->resourceByName(lastPreset); if (!preset) { preset = rserver->resourceByName("Basic_tip_default"); } if (!preset) { preset = rserver->resources().first(); } if (preset) { paintOpBox()->restoreResource(preset.data()); } const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor foreground(Qt::black, cs); d->canvasResourceProvider.setFGColor(cfg.readKoColor("LastForeGroundColor",foreground)); KoColor background(Qt::white, cs); d->canvasResourceProvider.setBGColor(cfg.readKoColor("LastBackGroundColor",background)); } KisCanvasController *canvasController = dynamic_cast(d->currentImageView->canvasController()); d->viewConnections.addUniqueConnection(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), doc->image(), SLOT(requestStrokeEndActiveNode())); d->viewConnections.addUniqueConnection(d->rotateCanvasRight, SIGNAL(triggered()), canvasController, SLOT(rotateCanvasRight15())); d->viewConnections.addUniqueConnection(d->rotateCanvasLeft, SIGNAL(triggered()),canvasController, SLOT(rotateCanvasLeft15())); d->viewConnections.addUniqueConnection(d->resetCanvasRotation, SIGNAL(triggered()),canvasController, SLOT(resetCanvasRotation())); d->viewConnections.addUniqueConnection(d->wrapAroundAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleWrapAroundMode(bool))); d->wrapAroundAction->setChecked(canvasController->wrapAroundMode()); d->viewConnections.addUniqueConnection(d->levelOfDetailAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleLevelOfDetailMode(bool))); d->levelOfDetailAction->setChecked(canvasController->levelOfDetailMode()); d->viewConnections.addUniqueConnection(d->currentImageView->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), d->controlFrame.paintopBox(), SLOT(slotColorSpaceChanged(const KoColorSpace*))); d->viewConnections.addUniqueConnection(d->showRulersAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setShowRulers(bool))); d->viewConnections.addUniqueConnection(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setRulersTrackMouse(bool))); d->viewConnections.addUniqueConnection(d->zoomTo100pct, SIGNAL(triggered()), imageView->zoomManager(), SLOT(zoomTo100())); d->viewConnections.addUniqueConnection(d->zoomIn, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomIn())); d->viewConnections.addUniqueConnection(d->zoomOut, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomOut())); d->viewConnections.addUniqueConnection(d->softProof, SIGNAL(toggled(bool)), view, SLOT(slotSoftProofing(bool)) ); d->viewConnections.addUniqueConnection(d->gamutCheck, SIGNAL(toggled(bool)), view, SLOT(slotGamutCheck(bool)) ); imageView->zoomManager()->setShowRulers(d->showRulersAction->isChecked()); imageView->zoomManager()->setRulersTrackMouse(d->rulersTrackMouseAction->isChecked()); showHideScrollbars(); } d->filterManager.setView(imageView); d->selectionManager.setView(imageView); d->guidesManager.setView(imageView); d->nodeManager.setView(imageView); d->imageManager.setView(imageView); d->canvasControlsManager.setView(imageView); d->actionManager.setView(imageView); d->gridManager.setView(imageView); d->statusBar.setView(imageView); d->paintingAssistantsManager.setView(imageView); d->mirrorManager.setView(imageView); if (d->currentImageView) { d->currentImageView->notifyCurrentStateChanged(true); d->currentImageView->canvasController()->activate(); d->currentImageView->canvasController()->setFocus(); } d->actionManager.updateGUI(); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), resourceProvider(), SLOT(slotImageSizeChanged())); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigResolutionChanged(double,double)), resourceProvider(), SLOT(slotOnScreenResolutionChanged())); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(updateGUI())); d->viewConnections.addUniqueConnection( view->zoomManager()->zoomController(), SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)), resourceProvider(), SLOT(slotOnScreenResolutionChanged())); resourceProvider()->slotImageSizeChanged(); resourceProvider()->slotOnScreenResolutionChanged(); Q_EMIT viewChanged(); } KoZoomController *KisViewManager::zoomController() const { if (d->currentImageView) { return d->currentImageView->zoomController(); } return 0; } KisImageWSP KisViewManager::image() const { if (document()) { return document()->image(); } return 0; } KisCanvasResourceProvider * KisViewManager::resourceProvider() { return &d->canvasResourceProvider; } KisCanvas2 * KisViewManager::canvasBase() const { if (d && d->currentImageView) { return d->currentImageView->canvasBase(); } return 0; } QWidget* KisViewManager::canvas() const { if (d && d->currentImageView && d->currentImageView->canvasBase()->canvasWidget()) { return d->currentImageView->canvasBase()->canvasWidget(); } return 0; } KisStatusBar * KisViewManager::statusBar() const { return &d->statusBar; } void KisViewManager::addStatusBarItem(QWidget *widget, int stretch, bool permanent) { d->statusBar.addStatusBarItem(widget, stretch, permanent); } void KisViewManager::removeStatusBarItem(QWidget *widget) { d->statusBar.removeStatusBarItem(widget); } KisPaintopBox* KisViewManager::paintOpBox() const { return d->controlFrame.paintopBox(); } KoProgressUpdater* KisViewManager::createProgressUpdater(KoProgressUpdater::Mode mode) { return new KisProgressUpdater(d->statusBar.progress(), document()->progressProxy(), mode); } KisSelectionManager * KisViewManager::selectionManager() { return &d->selectionManager; } KisNodeSP KisViewManager::activeNode() { return d->nodeManager.activeNode(); } KisLayerSP KisViewManager::activeLayer() { return d->nodeManager.activeLayer(); } KisPaintDeviceSP KisViewManager::activeDevice() { return d->nodeManager.activePaintDevice(); } KisZoomManager * KisViewManager::zoomManager() { if (d->currentImageView) { return d->currentImageView->zoomManager(); } return 0; } KisFilterManager * KisViewManager::filterManager() { return &d->filterManager; } KisImageManager * KisViewManager::imageManager() { return &d->imageManager; } KisInputManager* KisViewManager::inputManager() const { return &d->inputManager; } KisSelectionSP KisViewManager::selection() { if (d->currentImageView) { return d->currentImageView->selection(); } return 0; } bool KisViewManager::selectionEditable() { KisLayerSP layer = activeLayer(); if (layer) { KoProperties properties; QList masks = layer->childNodes(QStringList("KisSelectionMask"), properties); if (masks.size() == 1) { return masks[0]->isEditable(); } } // global selection is always editable return true; } KisUndoAdapter * KisViewManager::undoAdapter() { if (!document()) return 0; KisImageWSP image = document()->image(); Q_ASSERT(image); return image->undoAdapter(); } void KisViewManager::createActions() { KisConfig cfg; d->saveIncremental = actionManager()->createAction("save_incremental_version"); connect(d->saveIncremental, SIGNAL(triggered()), this, SLOT(slotSaveIncremental())); d->saveIncrementalBackup = actionManager()->createAction("save_incremental_backup"); connect(d->saveIncrementalBackup, SIGNAL(triggered()), this, SLOT(slotSaveIncrementalBackup())); connect(mainWindow(), SIGNAL(documentSaved()), this, SLOT(slotDocumentSaved())); d->saveIncremental->setEnabled(false); d->saveIncrementalBackup->setEnabled(false); KisAction *tabletDebugger = actionManager()->createAction("tablet_debugger"); connect(tabletDebugger, SIGNAL(triggered()), this, SLOT(toggleTabletLogger())); d->createTemplate = actionManager()->createAction("create_template"); connect(d->createTemplate, SIGNAL(triggered()), this, SLOT(slotCreateTemplate())); d->createCopy = actionManager()->createAction("create_copy"); connect(d->createCopy, SIGNAL(triggered()), this, SLOT(slotCreateCopy())); d->openResourcesDirectory = actionManager()->createAction("open_resources_directory"); connect(d->openResourcesDirectory, SIGNAL(triggered()), SLOT(openResourcesDirectory())); d->rotateCanvasRight = actionManager()->createAction("rotate_canvas_right"); d->rotateCanvasLeft = actionManager()->createAction("rotate_canvas_left"); d->resetCanvasRotation = actionManager()->createAction("reset_canvas_rotation"); d->wrapAroundAction = actionManager()->createAction("wrap_around_mode"); d->levelOfDetailAction = actionManager()->createAction("level_of_detail_mode"); d->softProof = actionManager()->createAction("softProof"); d->gamutCheck = actionManager()->createAction("gamutCheck"); KisAction *tAction = actionManager()->createAction("showStatusBar"); tAction->setChecked(cfg.showStatusBar()); connect(tAction, SIGNAL(toggled(bool)), this, SLOT(showStatusBar(bool))); tAction = actionManager()->createAction("view_show_canvas_only"); tAction->setChecked(false); connect(tAction, SIGNAL(toggled(bool)), this, SLOT(switchCanvasOnly(bool))); //Workaround, by default has the same shortcut as mirrorCanvas KisAction *a = dynamic_cast(actionCollection()->action("format_italic")); if (a) { a->setDefaultShortcut(QKeySequence()); } a = actionManager()->createAction("edit_blacklist_cleanup"); connect(a, SIGNAL(triggered()), this, SLOT(slotBlacklistCleanup())); d->showRulersAction = actionManager()->createAction("view_ruler"); d->showRulersAction->setChecked(cfg.showRulers()); connect(d->showRulersAction, SIGNAL(toggled(bool)), SLOT(slotSaveShowRulersState(bool))); d->rulersTrackMouseAction = actionManager()->createAction("rulers_track_mouse"); d->rulersTrackMouseAction->setChecked(cfg.rulersTrackMouse()); connect(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), SLOT(slotSaveRulersTrackMouseState(bool))); d->zoomTo100pct = actionManager()->createAction("zoom_to_100pct"); d->zoomIn = actionManager()->createStandardAction(KStandardAction::ZoomIn, 0, ""); d->zoomOut = actionManager()->createStandardAction(KStandardAction::ZoomOut, 0, ""); d->actionAuthor = new KSelectAction(KisIconUtils::loadIcon("im-user"), i18n("Active Author Profile"), this); connect(d->actionAuthor, SIGNAL(triggered(const QString &)), this, SLOT(changeAuthorProfile(const QString &))); actionCollection()->addAction("settings_active_author", d->actionAuthor); slotUpdateAuthorProfileActions(); } void KisViewManager::setupManagers() { // Create the managers for filters, selections, layers etc. // XXX: When the currentlayer changes, call updateGUI on all // managers d->filterManager.setup(actionCollection(), actionManager()); d->selectionManager.setup(actionManager()); d->guidesManager.setup(actionManager()); d->nodeManager.setup(actionCollection(), actionManager()); d->imageManager.setup(actionManager()); d->gridManager.setup(actionManager()); d->paintingAssistantsManager.setup(actionManager()); d->canvasControlsManager.setup(actionManager()); d->mirrorManager.setup(actionCollection()); d->scriptManager.setup(actionCollection(), actionManager()); } void KisViewManager::updateGUI() { d->guiUpdateCompressor.start(); } void KisViewManager::slotBlacklistCleanup() { KisDlgBlacklistCleanup dialog; dialog.exec(); } KisNodeManager * KisViewManager::nodeManager() const { return &d->nodeManager; } KisActionManager* KisViewManager::actionManager() const { return &d->actionManager; } KisGridManager * KisViewManager::gridManager() const { return &d->gridManager; } KisGuidesManager * KisViewManager::guidesManager() const { return &d->guidesManager; } KisPaintingAssistantsManager* KisViewManager::paintingAssistantsManager() const { return &d->paintingAssistantsManager; } KisDocument *KisViewManager::document() const { if (d->currentImageView && d->currentImageView->document()) { return d->currentImageView->document(); } return 0; } KisScriptManager *KisViewManager::scriptManager() const { return &d->scriptManager; } int KisViewManager::viewCount() const { KisMainWindow *mw = qobject_cast(d->mainWindow); if (mw) { return mw->viewCount(); } return 0; } -bool KisViewManager::KisViewManagerPrivate::blockUntillOperationsFinishedImpl(KisImageSP image, bool force) +bool KisViewManager::KisViewManagerPrivate::blockUntilOperationsFinishedImpl(KisImageSP image, bool force) { const int busyWaitDelay = 1000; KisDelayedSaveDialog dialog(image, !force ? KisDelayedSaveDialog::GeneralDialog : KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, mainWindow); dialog.blockIfImageIsBusy(); return dialog.result() == QDialog::Accepted; } -bool KisViewManager::blockUntillOperationsFinished(KisImageSP image) +bool KisViewManager::blockUntilOperationsFinished(KisImageSP image) { - return d->blockUntillOperationsFinishedImpl(image, false); + return d->blockUntilOperationsFinishedImpl(image, false); } void KisViewManager::blockUntillOperationsFinishedForced(KisImageSP image) { - d->blockUntillOperationsFinishedImpl(image, true); + d->blockUntilOperationsFinishedImpl(image, true); } void KisViewManager::slotCreateTemplate() { if (!document()) return; KisTemplateCreateDia::createTemplate( QStringLiteral("templates/"), ".kra", document(), mainWindow()); } void KisViewManager::slotCreateCopy() { if (!document()) return; KisDocument *doc = KisPart::instance()->createDocument(); QString name = document()->documentInfo()->aboutInfo("name"); if (name.isEmpty()) { name = document()->url().toLocalFile(); } name = i18n("%1 (Copy)", name); doc->documentInfo()->setAboutInfo("title", name); KisImageWSP image = document()->image(); KisImageSP newImage = new KisImage(doc->createUndoStore(), image->width(), image->height(), image->colorSpace(), name); newImage->setRootLayer(dynamic_cast(image->rootLayer()->clone().data())); doc->setCurrentImage(newImage); KisPart::instance()->addDocument(doc); KisMainWindow *mw = qobject_cast(d->mainWindow); mw->addViewAndNotifyLoadingCompleted(doc); } QMainWindow* KisViewManager::qtMainWindow() const { if (d->mainWindow) return d->mainWindow; //Fallback for when we have not yet set the main window. QMainWindow* w = qobject_cast(qApp->activeWindow()); if(w) return w; return mainWindow(); } void KisViewManager::setQtMainWindow(QMainWindow* newMainWindow) { d->mainWindow = newMainWindow; } void KisViewManager::slotDocumentSaved() { d->saveIncremental->setEnabled(true); d->saveIncrementalBackup->setEnabled(true); } void KisViewManager::slotSaveIncremental() { if (!document()) return; bool foundVersion; bool fileAlreadyExists; bool isBackup; QString version = "000"; QString newVersion; QString letter; QString fileName = document()->localFilePath(); // Find current version filenames // v v Regexp to find incremental versions in the filename, taking our backup scheme into account as well // Considering our incremental version and backup scheme, format is filename_001~001.ext QRegExp regex("_\\d{1,4}[.]|_\\d{1,4}[a-z][.]|_\\d{1,4}[~]|_\\d{1,4}[a-z][~]"); regex.indexIn(fileName); // Perform the search QStringList matches = regex.capturedTexts(); foundVersion = matches.at(0).isEmpty() ? false : true; // Ensure compatibility with Save Incremental Backup // If this regex is not kept separate, the entire algorithm needs modification; // It's simpler to just add this. QRegExp regexAux("_\\d{1,4}[~]|_\\d{1,4}[a-z][~]"); regexAux.indexIn(fileName); // Perform the search QStringList matchesAux = regexAux.capturedTexts(); isBackup = matchesAux.at(0).isEmpty() ? false : true; // If the filename has a version, prepare it for incrementation if (foundVersion) { version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches if (version.contains(QRegExp("[a-z]"))) { version.chop(1); // Trim "." letter = version.right(1); // Save letter version.chop(1); // Trim letter } else { version.chop(1); // Trim "." } version.remove(0, 1); // Trim "_" } else { // ...else, simply add a version to it so the next loop works QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension regex2.indexIn(fileName); QStringList matches2 = regex2.capturedTexts(); QString extensionPlusVersion = matches2.at(0); extensionPlusVersion.prepend(version); extensionPlusVersion.prepend("_"); fileName.replace(regex2, extensionPlusVersion); } // Prepare the base for new version filename int intVersion = version.toInt(0); ++intVersion; QString baseNewVersion = QString::number(intVersion); while (baseNewVersion.length() < version.length()) { baseNewVersion.prepend("0"); } // Check if the file exists under the new name and search until options are exhausted (test appending a to z) do { newVersion = baseNewVersion; newVersion.prepend("_"); if (!letter.isNull()) newVersion.append(letter); if (isBackup) { newVersion.append("~"); } else { newVersion.append("."); } fileName.replace(regex, newVersion); fileAlreadyExists = QFile(fileName).exists(); if (fileAlreadyExists) { if (!letter.isNull()) { char letterCh = letter.at(0).toLatin1(); ++letterCh; letter = QString(QChar(letterCh)); } else { letter = 'a'; } } } while (fileAlreadyExists && letter != "{"); // x, y, z, {... if (letter == "{") { QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental version"), i18n("Alternative names exhausted, try manually saving with a higher number")); return; } document()->setFileBatchMode(true); document()->saveAs(QUrl::fromUserInput(fileName)); document()->setFileBatchMode(false); if (mainWindow()) { mainWindow()->updateCaption(); } } void KisViewManager::slotSaveIncrementalBackup() { if (!document()) return; bool workingOnBackup; bool fileAlreadyExists; QString version = "000"; QString newVersion; QString letter; QString fileName = document()->localFilePath(); // First, discover if working on a backup file, or a normal file QRegExp regex("~\\d{1,4}[.]|~\\d{1,4}[a-z][.]"); regex.indexIn(fileName); // Perform the search QStringList matches = regex.capturedTexts(); workingOnBackup = matches.at(0).isEmpty() ? false : true; if (workingOnBackup) { // Try to save incremental version (of backup), use letter for alt versions version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches if (version.contains(QRegExp("[a-z]"))) { version.chop(1); // Trim "." letter = version.right(1); // Save letter version.chop(1); // Trim letter } else { version.chop(1); // Trim "." } version.remove(0, 1); // Trim "~" // Prepare the base for new version filename int intVersion = version.toInt(0); ++intVersion; QString baseNewVersion = QString::number(intVersion); QString backupFileName = document()->localFilePath(); while (baseNewVersion.length() < version.length()) { baseNewVersion.prepend("0"); } // Check if the file exists under the new name and search until options are exhausted (test appending a to z) do { newVersion = baseNewVersion; newVersion.prepend("~"); if (!letter.isNull()) newVersion.append(letter); newVersion.append("."); backupFileName.replace(regex, newVersion); fileAlreadyExists = QFile(backupFileName).exists(); if (fileAlreadyExists) { if (!letter.isNull()) { char letterCh = letter.at(0).toLatin1(); ++letterCh; letter = QString(QChar(letterCh)); } else { letter = 'a'; } } } while (fileAlreadyExists && letter != "{"); // x, y, z, {... if (letter == "{") { QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental backup"), i18n("Alternative names exhausted, try manually saving with a higher number")); return; } QFile::copy(fileName, backupFileName); document()->saveAs(QUrl::fromUserInput(fileName)); if (mainWindow()) mainWindow()->updateCaption(); } else { // if NOT working on a backup... // Navigate directory searching for latest backup version, ignore letters const quint8 HARDCODED_DIGIT_COUNT = 3; QString baseNewVersion = "000"; QString backupFileName = document()->localFilePath(); QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension regex2.indexIn(backupFileName); QStringList matches2 = regex2.capturedTexts(); QString extensionPlusVersion = matches2.at(0); extensionPlusVersion.prepend(baseNewVersion); extensionPlusVersion.prepend("~"); backupFileName.replace(regex2, extensionPlusVersion); // Save version with 1 number higher than the highest version found ignoring letters do { newVersion = baseNewVersion; newVersion.prepend("~"); newVersion.append("."); backupFileName.replace(regex, newVersion); fileAlreadyExists = QFile(backupFileName).exists(); if (fileAlreadyExists) { // Prepare the base for new version filename, increment by 1 int intVersion = baseNewVersion.toInt(0); ++intVersion; baseNewVersion = QString::number(intVersion); while (baseNewVersion.length() < HARDCODED_DIGIT_COUNT) { baseNewVersion.prepend("0"); } } } while (fileAlreadyExists); // Save both as backup and on current file for interapplication workflow document()->setFileBatchMode(true); QFile::copy(fileName, backupFileName); document()->saveAs(QUrl::fromUserInput(fileName)); document()->setFileBatchMode(false); if (mainWindow()) mainWindow()->updateCaption(); } } void KisViewManager::disableControls() { // prevents possible crashes, if somebody changes the paintop during dragging by using the mousewheel // this is for Bug 250944 // the solution blocks all wheel, mouse and key event, while dragging with the freehand tool // see KisToolFreehand::initPaint() and endPaint() d->controlFrame.paintopBox()->installEventFilter(&d->blockingEventFilter); Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) { child->installEventFilter(&d->blockingEventFilter); } } void KisViewManager::enableControls() { d->controlFrame.paintopBox()->removeEventFilter(&d->blockingEventFilter); Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) { child->removeEventFilter(&d->blockingEventFilter); } } void KisViewManager::showStatusBar(bool toggled) { KisMainWindow *mw = mainWindow(); if(mw && mw->statusBar()) { mw->statusBar()->setVisible(toggled); KisConfig cfg; cfg.setShowStatusBar(toggled); } } void KisViewManager::switchCanvasOnly(bool toggled) { KisConfig cfg; KisMainWindow* main = mainWindow(); if(!main) { dbgUI << "Unable to switch to canvas-only mode, main window not found"; return; } if (toggled) { d->canvasState = qtMainWindow()->saveState(); } if (cfg.hideStatusbarFullscreen()) { if (main->statusBar()) { if (!toggled) { if (main->statusBar()->dynamicPropertyNames().contains("wasvisible")) { if (main->statusBar()->property("wasvisible").toBool()) { main->statusBar()->setVisible(true); } } } else { main->statusBar()->setProperty("wasvisible", main->statusBar()->isVisible()); main->statusBar()->setVisible(false); } } } if (cfg.hideDockersFullscreen()) { KisAction* action = qobject_cast(main->actionCollection()->action("view_toggledockers")); if (action) { action->setCheckable(true); if (toggled) { if (action->isChecked()) { cfg.setShowDockers(action->isChecked()); action->setChecked(false); } else { cfg.setShowDockers(false); } } else { action->setChecked(cfg.showDockers()); } } } if (cfg.hideTitlebarFullscreen() && !cfg.fullscreenMode()) { if(toggled) { main->setWindowState( main->windowState() | Qt::WindowFullScreen); } else { main->setWindowState( main->windowState() & ~Qt::WindowFullScreen); } } if (cfg.hideMenuFullscreen()) { if (!toggled) { if (main->menuBar()->dynamicPropertyNames().contains("wasvisible")) { if (main->menuBar()->property("wasvisible").toBool()) { main->menuBar()->setVisible(true); } } } else { main->menuBar()->setProperty("wasvisible", main->menuBar()->isVisible()); main->menuBar()->setVisible(false); } } if (cfg.hideToolbarFullscreen()) { QList toolBars = main->findChildren(); Q_FOREACH (QToolBar* toolbar, toolBars) { if (!toggled) { if (toolbar->dynamicPropertyNames().contains("wasvisible")) { if (toolbar->property("wasvisible").toBool()) { toolbar->setVisible(true); } } } else { toolbar->setProperty("wasvisible", toolbar->isVisible()); toolbar->setVisible(false); } } } showHideScrollbars(); if (toggled) { // show a fading heads-up display about the shortcut to go back showFloatingMessage(i18n("Going into Canvas-Only mode.\nPress %1 to go back.", actionCollection()->action("view_show_canvas_only")->shortcut().toString()), QIcon()); } else { main->restoreState(d->canvasState); } } void KisViewManager::toggleTabletLogger() { d->inputManager.toggleTabletLogger(); } void KisViewManager::openResourcesDirectory() { QString dir = KoResourcePaths::locateLocal("data", ""); QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); } void KisViewManager::updateIcons() { if (mainWindow()) { QList dockers = mainWindow()->dockWidgets(); Q_FOREACH (QDockWidget* dock, dockers) { dbgKrita << "name " << dock->objectName(); KoDockWidgetTitleBar* titlebar = dynamic_cast(dock->titleBarWidget()); if (titlebar) { titlebar->updateIcons(); } QObjectList objects; objects.append(dock); while (!objects.isEmpty()) { QObject* object = objects.takeFirst(); objects.append(object->children()); KisIconUtils::updateIconCommon(object); } } } } void KisViewManager::initializeStatusBarVisibility() { KisConfig cfg; d->mainWindow->statusBar()->setVisible(cfg.showStatusBar()); } void KisViewManager::guiUpdateTimeout() { d->nodeManager.updateGUI(); d->selectionManager.updateGUI(); d->filterManager.updateGUI(); if (zoomManager()) { zoomManager()->updateGUI(); } d->gridManager.updateGUI(); d->actionManager.updateGUI(); } void KisViewManager::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment) { if (!d->currentImageView) return; d->currentImageView->showFloatingMessageImpl(message, icon, timeout, priority, alignment); emit floatingMessageRequested(message, icon.name()); } KisMainWindow *KisViewManager::mainWindow() const { return qobject_cast(d->mainWindow); } void KisViewManager::showHideScrollbars() { if (!d->currentImageView) return; if (!d->currentImageView->canvasController()) return; KisConfig cfg; bool toggled = actionCollection()->action("view_show_canvas_only")->isChecked(); if ( (toggled && cfg.hideScrollbarsFullscreen()) || (!toggled && cfg.hideScrollbars()) ) { d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } else { d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } void KisViewManager::slotSaveShowRulersState(bool value) { KisConfig cfg; cfg.setShowRulers(value); } void KisViewManager::slotSaveRulersTrackMouseState(bool value) { KisConfig cfg; cfg.setRulersTrackMouse(value); } void KisViewManager::setShowFloatingMessage(bool show) { d->showFloatingMessage = show; } void KisViewManager::changeAuthorProfile(const QString &profileName) { KConfigGroup appAuthorGroup(KoGlobal::calligraConfig(), "Author"); if (profileName.isEmpty()) { appAuthorGroup.writeEntry("active-profile", ""); } else if (profileName == i18nc("choice for author profile", "Anonymous")) { appAuthorGroup.writeEntry("active-profile", "anonymous"); } else { appAuthorGroup.writeEntry("active-profile", profileName); } appAuthorGroup.sync(); Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) { doc->documentInfo()->updateParameters(); } } void KisViewManager::slotUpdateAuthorProfileActions() { Q_ASSERT(d->actionAuthor); if (!d->actionAuthor) { return; } d->actionAuthor->clear(); d->actionAuthor->addAction(i18n("Default Author Profile")); d->actionAuthor->addAction(i18nc("choice for author profile", "Anonymous")); KConfigGroup authorGroup(KoGlobal::calligraConfig(), "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); Q_FOREACH (const QString &profile , profiles) { d->actionAuthor->addAction(profile); } KConfigGroup appAuthorGroup(KoGlobal::calligraConfig(), "Author"); QString profileName = appAuthorGroup.readEntry("active-profile", ""); if (profileName == "anonymous") { d->actionAuthor->setCurrentItem(1); } else if (profiles.contains(profileName)) { d->actionAuthor->setCurrentAction(profileName); } else { d->actionAuthor->setCurrentItem(0); } } diff --git a/libs/ui/KisViewManager.h b/libs/ui/KisViewManager.h index 2264ebb37f..8e9d75e36b 100644 --- a/libs/ui/KisViewManager.h +++ b/libs/ui/KisViewManager.h @@ -1,272 +1,272 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_GUI_CLIENT_H #define KIS_GUI_CLIENT_H #include #include #include #include #include #include #include #include #include "kis_floating_message.h" class QPoint; class KisView; class KisCanvas2; class KisCanvasResourceProvider; class KisDocument; class KisFilterManager; class KisGridManager; class KisGuidesManager; class KisImageManager; class KisNodeManager; class KisPaintingAssistantsManager; class KisPaintopBox; class KisSelectionManager; class KisStatusBar; class KisUndoAdapter; class KisZoomManager; class KisPaintopBox; class KisActionManager; class KisScriptManager; class KisInputManager; /** * Krita view class * * Following the broad model-view-controller idea this class shows you one view on the document. * There can be multiple views of the same document each in with independent settings for viewMode and zoom etc. */ class KRITAUI_EXPORT KisViewManager : public QObject { Q_OBJECT public: /** * Construct a new view on the krita document. * @param document the document we show. * @param parent a parent widget we show ourselves in. */ KisViewManager(QWidget *parent, KActionCollection *actionCollection); virtual ~KisViewManager(); /** * Retrieves the entire action collection. */ virtual KActionCollection* actionCollection() const; public: // Krita specific interfaces void setCurrentView(KisView *view); /// Return the image this view is displaying KisImageWSP image() const; KoZoomController *zoomController() const; /// The resource provider contains all per-view settings, such as /// current color, current paint op etc. KisCanvasResourceProvider * resourceProvider(); /// Return the canvasbase class KisCanvas2 * canvasBase() const; /// Return the actual widget that is displaying the current image QWidget* canvas() const; /// Return the wrapper class around the statusbar KisStatusBar * statusBar() const; /** * This adds a widget to the statusbar for this view. * If you use this method instead of using statusBar() directly, * KisView will take care of removing the items when the view GUI is deactivated * and readding them when it is reactivated. * The parameters are the same as QStatusBar::addWidget(). */ void addStatusBarItem(QWidget * widget, int stretch = 0, bool permanent = false); /** * Remove a widget from the statusbar for this view. */ void removeStatusBarItem(QWidget * widget); KisPaintopBox* paintOpBox() const; /// create a new progress updater KoProgressUpdater *createProgressUpdater(KoProgressUpdater::Mode mode = KoProgressUpdater::Threaded); /// The selection manager handles everything action related to /// selections. KisSelectionManager *selectionManager(); /// The node manager handles everything about nodes KisNodeManager *nodeManager() const; KisActionManager *actionManager() const; /** * Convenience method to get at the active node, which may be * a layer or a mask or a selection */ KisNodeSP activeNode(); /// Convenience method to get at the active layer KisLayerSP activeLayer(); /// Convenience method to get at the active paint device KisPaintDeviceSP activeDevice(); /// The filtermanager handles everything action-related to filters KisFilterManager *filterManager(); /// The image manager handles everything action-related to the /// current image KisImageManager *imageManager(); /// Filters events and sends them to canvas actions KisInputManager *inputManager() const; /// Convenience method to get at the active selection (the /// selection of the current layer, or, if that does not exist, /// the global selection. KisSelectionSP selection(); /// Checks if the current global or local selection is editable bool selectionEditable(); /// The undo adapter is used to add commands to the undo stack KisUndoAdapter *undoAdapter(); KisDocument *document() const; KisScriptManager *scriptManager() const; int viewCount() const; /** * @brief blockUntillOperationsFinished blocks the GUI of the application until execution * of actions on \p image is finished * @param image the image which we should wait for * @return true if the image has finished execution of the actions, false if * the user cancelled operation */ - bool blockUntillOperationsFinished(KisImageSP image); + bool blockUntilOperationsFinished(KisImageSP image); /** * @brief blockUntillOperationsFinished blocks the GUI of the application until execution * of actions on \p image is finished. Does *not* provide a "Cancel" button. So the * user is forced to wait. * @param image the image which we should wait for */ void blockUntillOperationsFinishedForced(KisImageSP image); public: KisGridManager * gridManager() const; KisGuidesManager * guidesManager() const; KisPaintingAssistantsManager* paintingAssistantsManager() const; /// disable and enable toolbar controls. used for disabling them during painting. void enableControls(); void disableControls(); /// shows a floating message in the top right corner of the canvas void showFloatingMessage(const QString &message, const QIcon& icon, int timeout = 4500, KisFloatingMessage::Priority priority = KisFloatingMessage::Medium, int alignment = Qt::AlignCenter | Qt::TextWordWrap); /// @return the KoMaindow this view is in, or 0 KisMainWindow *mainWindow() const; /// The QMainWindow associated with this view. This is most likely going to be shell(), but /// when running as Gemini or Sketch, this will be set to the applications' own QMainWindow. /// This can be checked by qobject_casting to KisMainWindow to check the difference. QMainWindow* qtMainWindow() const; /// The mainWindow function will return the shell() value, unless this function is called /// with a non-null value. To make it return shell() again, simply pass null to this function. void setQtMainWindow(QMainWindow* newMainWindow); public Q_SLOTS: void switchCanvasOnly(bool toggled); void setShowFloatingMessage(bool show); void showHideScrollbars(); /// Visit all managers to update gui elements, e.g. enable / disable actions. /// This is heavy-duty call, so it uses a compressor. void updateGUI(); /// Update the style of all the icons void updateIcons(); void slotViewAdded(KisView *view); void slotViewRemoved(KisView *view); Q_SIGNALS: void floatingMessageRequested(const QString &message, const QString &iconName); /** * @brief viewChanged * sent out when the view has changed. */ void viewChanged(); private Q_SLOTS: void slotBlacklistCleanup(); void slotCreateTemplate(); void slotCreateCopy(); void slotDocumentSaved(); void slotSaveIncremental(); void slotSaveIncrementalBackup(); void showStatusBar(bool toggled); void toggleTabletLogger(); void openResourcesDirectory(); void initializeStatusBarVisibility(); void guiUpdateTimeout(); void changeAuthorProfile(const QString &profileName); void slotUpdateAuthorProfileActions(); void slotSaveShowRulersState(bool value); void slotSaveRulersTrackMouseState(bool value); private: void createActions(); void setupManagers(); /// The zoommanager handles everything action-related to zooming KisZoomManager * zoomManager(); private: class KisViewManagerPrivate; KisViewManagerPrivate * const d; }; #endif diff --git a/libs/ui/actions/kis_selection_action_factories.cpp b/libs/ui/actions/kis_selection_action_factories.cpp index cd44168a82..8a10cadcac 100644 --- a/libs/ui/actions/kis_selection_action_factories.cpp +++ b/libs/ui/actions/kis_selection_action_factories.cpp @@ -1,643 +1,643 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection_action_factories.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_pixel_selection.h" #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "kis_fill_painter.h" #include "kis_transaction.h" #include "kis_iterator_ng.h" #include "kis_processing_applicator.h" #include "kis_group_layer.h" #include "commands/kis_selection_commands.h" #include "commands/kis_image_layer_add_command.h" #include "kis_tool_proxy.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_selection_manager.h" #include "kis_transaction_based_command.h" #include "kis_selection_filters.h" #include "kis_shape_selection.h" #include "KisPart.h" #include "kis_shape_layer.h" #include #include "kis_import_catcher.h" #include #include #include "kis_canvas_resource_provider.h" #include "kis_figure_painting_tool_helper.h" namespace ActionHelper { void copyFromDevice(KisViewManager *view, KisPaintDeviceSP device, bool makeSharpClip = false) { KisImageWSP image = view->image(); if (!image) return; KisSelectionSP selection = view->selection(); QRect rc = (selection) ? selection->selectedExactRect() : image->bounds(); KisPaintDeviceSP clip = new KisPaintDevice(device->colorSpace()); Q_CHECK_PTR(clip); const KoColorSpace *cs = clip->colorSpace(); // TODO if the source is linked... copy from all linked layers?!? // Copy image data KisPainter::copyAreaOptimized(QPoint(), device, clip, rc); if (selection) { // Apply selection mask. KisPaintDeviceSP selectionProjection = selection->projection(); KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width()); KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); const KoColorSpace *selCs = selection->projection()->colorSpace(); for (qint32 y = 0; y < rc.height(); y++) { for (qint32 x = 0; x < rc.width(); x++) { /** * Sharp method is an exact reverse of COMPOSITE_OVER * so if you cover the cut/copied piece over its source * you get an exactly the same image without any seams */ if (makeSharpClip) { qreal dstAlpha = cs->opacityF(layerIt->rawData()); qreal sel = selCs->opacityF(selectionIt->oldRawData()); qreal newAlpha = sel * dstAlpha / (1.0 - dstAlpha + sel * dstAlpha); float mask = newAlpha / dstAlpha; cs->applyAlphaNormedFloatMask(layerIt->rawData(), &mask, 1); } else { cs->applyAlphaU8Mask(layerIt->rawData(), selectionIt->oldRawData(), 1); } layerIt->nextPixel(); selectionIt->nextPixel(); } layerIt->nextRow(); selectionIt->nextRow(); } } KisClipboard::instance()->setClip(clip, rc.topLeft()); } } void KisSelectAllActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Select All")); if (!image->globalSelection()) { ap->applyCommand(new KisSetEmptyGlobalSelectionCommand(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } struct SelectAll : public KisTransactionBasedCommand { SelectAll(KisImageSP image) : m_image(image) {} KisImageSP m_image; KUndo2Command* paint() override { KisSelectionSP selection = m_image->globalSelection(); KisSelectionTransaction transaction(selection->pixelSelection()); selection->pixelSelection()->select(m_image->bounds()); return transaction.endAndTake(); } }; ap->applyCommand(new SelectAll(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisDeselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisDeselectGlobalSelectionCommand(image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisReselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisReselectGlobalSelectionCommand(image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisFillActionFactory::run(const QString &fillSource, KisViewManager *view) { KisNodeSP node = view->activeNode(); if (!node || !node->hasEditablePaintDevice()) return; KisSelectionSP selection = view->selection(); QRect selectedRect = selection ? selection->selectedRect() : view->image()->bounds(); Q_UNUSED(selectedRect); KisPaintDeviceSP filled = node->paintDevice()->createCompositionSourceDevice(); Q_UNUSED(filled); bool usePattern = false; bool useBgColor = false; if (fillSource.contains("pattern")) { usePattern = true; } else if (fillSource.contains("bg")) { useBgColor = true; } KisProcessingApplicator applicator(view->image(), node, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Flood Fill Layer")); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(view->image(), node, view->resourceProvider()->resourceManager()); if (!fillSource.contains("opacity")) { resources->setOpacity(1.0); } KisProcessingVisitorSP visitor = new FillProcessingVisitor(QPoint(0, 0), // start position selection, resources, false, // fast mode usePattern, true, // fill only selection, 0, // feathering radius 0, // sizemod 80, // threshold, false, // unmerged useBgColor); applicator.applyVisitor(visitor, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); } void KisClearActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Clear action" view->canvasBase()->toolProxy()->deleteSelection(); } void KisImageResizeToSelectionActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Image Resize To Selection action" KisSelectionSP selection = view->selection(); if (!selection) return; view->image()->cropImage(selection->selectedExactRect()); } void KisCutCopyActionFactory::run(bool willCut, bool makeSharpClip, KisViewManager *view) { KisImageSP image = view->image(); if (!image) return; bool haveShapesSelected = view->selectionManager()->haveShapesSelected(); if (haveShapesSelected) { // XXX: "Add saving of XML data for Cut/Copy of shapes" KisImageBarrierLocker locker(image); if (willCut) { view->canvasBase()->toolProxy()->cut(); } else { view->canvasBase()->toolProxy()->copy(); } } else { KisNodeSP node = view->activeNode(); if (!node) return; KisSelectionSP selection = view->selection(); if (selection.isNull()) return; { KisImageBarrierLocker locker(image); KisPaintDeviceSP dev = node->paintDevice(); if (!dev) { dev = node->projection(); } if (!dev) { view->showFloatingMessage( i18nc("floating message when cannot copy from a node", "Cannot copy pixels from this type of layer "), QIcon(), 3000, KisFloatingMessage::Medium); return; } if (dev->exactBounds().isEmpty()) { view->showFloatingMessage( i18nc("floating message when copying empty selection", "Selection is empty: no pixels were copied "), QIcon(), 3000, KisFloatingMessage::Medium); return; } ActionHelper::copyFromDevice(view, dev, makeSharpClip); } if (willCut) { KUndo2Command *command = 0; if (node->hasEditablePaintDevice()) { struct ClearSelection : public KisTransactionBasedCommand { ClearSelection(KisNodeSP node, KisSelectionSP sel) : m_node(node), m_sel(sel) {} KisNodeSP m_node; KisSelectionSP m_sel; KUndo2Command* paint() override { KisSelectionSP cutSelection = m_sel; // Shrinking the cutting area was previously used // for getting seamless cut-paste. Now we use makeSharpClip // instead. // QRect originalRect = cutSelection->selectedExactRect(); // static const int preciseSelectionThreshold = 16; // // if (originalRect.width() > preciseSelectionThreshold || // originalRect.height() > preciseSelectionThreshold) { // cutSelection = new KisSelection(*m_sel); // delete cutSelection->flatten(); // // KisSelectionFilter* filter = new KisShrinkSelectionFilter(1, 1, false); // // QRect processingRect = filter->changeRect(originalRect); // filter->process(cutSelection->pixelSelection(), processingRect); // } KisTransaction transaction(m_node->paintDevice()); m_node->paintDevice()->clearSelection(cutSelection); m_node->setDirty(cutSelection->selectedRect()); return transaction.endAndTake(); } }; command = new ClearSelection(node, selection); } KUndo2MagicString actionName = willCut ? kundo2_i18n("Cut") : kundo2_i18n("Copy"); KisProcessingApplicator *ap = beginAction(view, actionName); if (command) { ap->applyCommand(command, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } KisOperationConfiguration config(id()); config.setProperty("will-cut", willCut); endAction(ap, config.toXML()); } } } void KisCopyMergedActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; - if (!view->blockUntillOperationsFinished(image)) return; + if (!view->blockUntilOperationsFinished(image)) return; image->barrierLock(); KisPaintDeviceSP dev = image->root()->projection(); ActionHelper::copyFromDevice(view, dev); image->unlock(); KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Copy Merged")); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisPasteActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KisPaintDeviceSP clip = KisClipboard::instance()->clip(image->bounds(), true); if (clip) { KisImportCatcher::adaptClipToImageColorSpace(clip, image); KisPaintLayer *newLayer = new KisPaintLayer(image.data(), image->nextLayerName() + i18n("(pasted)"), OPACITY_OPAQUE_U8, clip); KisNodeSP aboveNode = view->activeLayer(); KisNodeSP parentNode = aboveNode ? aboveNode->parent() : image->root(); KUndo2Command *cmd = new KisImageLayerAddCommand(image, newLayer, parentNode, aboveNode); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); endAction(ap, KisOperationConfiguration(id()).toXML()); } else { // XXX: "Add saving of XML data for Paste of shapes" view->canvasBase()->toolProxy()->paste(); } } void KisPasteNewActionFactory::run(KisViewManager *viewManager) { Q_UNUSED(viewManager); KisPaintDeviceSP clip = KisClipboard::instance()->clip(QRect(), true); if (!clip) return; QRect rect = clip->exactBounds(); if (rect.isEmpty()) return; KisDocument *doc = KisPart::instance()->createDocument(); KisImageSP image = new KisImage(doc->createUndoStore(), rect.width(), rect.height(), clip->colorSpace(), i18n("Pasted")); KisPaintLayerSP layer = new KisPaintLayer(image.data(), image->nextLayerName() + i18n("(pasted)"), OPACITY_OPAQUE_U8, clip->colorSpace()); KisPainter::copyAreaOptimized(QPoint(), clip, layer->paintDevice(), rect); image->addNode(layer.data(), image->rootLayer()); doc->setCurrentImage(image); KisPart::instance()->addDocument(doc); KisMainWindow *win = viewManager->mainWindow(); win->addViewAndNotifyLoadingCompleted(doc); } void KisInvertSelectionOperation::runFromXML(KisViewManager* view, const KisOperationConfiguration& config) { KisSelectionFilter* filter = new KisInvertSelectionFilter(); runFilter(filter, view, config); } void KisSelectionToVectorActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (selection->hasShapeSelection() || !selection->outlineCacheValid()) { return; } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); /** * Mark a shape that it belongs to a shape selection */ if(!shape->userData()) { shape->setUserData(new KisShapeSelectionMarker); } KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection")); ap->applyCommand(view->canvasBase()->shapeController()->addShape(shape), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } class KisShapeSelectionPaste : public KoOdfPaste { public: KisShapeSelectionPaste(KisViewManager* view) : m_view(view) { } ~KisShapeSelectionPaste() override { } bool process(const KoXmlElement & body, KoOdfReadStore & odfStore) override { KoOdfLoadingContext loadingContext(odfStore.styles(), odfStore.store()); KoShapeLoadingContext context(loadingContext, m_view->canvasBase()->shapeController()->resourceManager()); KoXmlElement child; QList shapes; forEachElement(child, body) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, context); if (shape) { shapes.append(shape); } } if (!shapes.isEmpty()) { KisSelectionToolHelper helper(m_view->canvasBase(), kundo2_i18n("Convert shapes to vector selection")); helper.addSelectionShapes(shapes); } return true; } private: KisViewManager* m_view; }; void KisShapesToVectorSelectionActionFactory::run(KisViewManager* view) { QList shapes = view->canvasBase()->shapeManager()->selection()->selectedShapes(); KoShapeOdfSaveHelper saveHelper(shapes); KoDrag drag; drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper); QMimeData* mimeData = drag.mimeData(); Q_ASSERT(mimeData->hasFormat(KoOdf::mimeType(KoOdf::Text))); KisShapeSelectionPaste paste(view); paste.paste(KoOdf::Text, mimeData); } void KisSelectionToShapeActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (!selection->outlineCacheValid()) { return; } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); KoColor fgColor = view->canvasBase()->resourceManager()->resource(KoCanvasResourceManager::ForegroundColor).value(); KoShapeStroke* border = new KoShapeStroke(1.0, fgColor.toQColor()); shape->setStroke(border); view->document()->shapeController()->addShape(shape); } void KisStrokeSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } int size = params.lineSize; KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } QPainterPath outline = pixelSelection->outlineCache(); QColor color = params.color.toQColor(); KisNodeSP currentNode = view->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->childCount() == 0) { KoCanvasResourceManager * rManager = view->resourceProvider()->resourceManager(); KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = params.fillStyle(); KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager , strokeStyle, fillStyle); helper.setFGColorOverride(params.color); helper.setSelectionOverride(0); QPen pen(Qt::red, size); pen.setJoinStyle(Qt::RoundJoin); if (fillStyle != KisPainter::FillStyleNone) { helper.paintPainterPathQPenFill(outline, pen, params.fillColor); } else { helper.paintPainterPathQPen(outline, pen, params.fillColor); } } else { QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(outline)); shape->setShapeId(KoPathShapeId); KoShapeStroke* border = new KoShapeStroke(size, color); shape->setStroke(border); view->document()->shapeController()->addShape(shape); } image->setModified(); } void KisStrokeBrushSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } KisNodeSP currentNode = view->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->childCount() == 0) { KoCanvasResourceManager * rManager = view->resourceProvider()->resourceManager(); QPainterPath outline = pixelSelection->outlineCache(); KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = KisPainter::FillStyleNone; KoColor color = params.color; KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager , strokeStyle, fillStyle); helper.setFGColorOverride(color); helper.setSelectionOverride(0); helper.paintPainterPath(outline); image->setModified(); } } diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc index 3003738cec..81b758ed46 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1102 +1,1116 @@ /* * preferencesdlg.cc - part of KImageShop * * Copyright (c) 1999 Michael Koch * Copyright (c) 2003-2011 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dlg_preferences.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include "KoID.h" #include #include #include #include #include #include "kis_action_registry.h" #include "widgets/squeezedcombobox.h" #include "kis_clipboard.h" #include "widgets/kis_cmb_idlist.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "KoColorConversionTransformation.h" #include "kis_cursor.h" #include "kis_config.h" #include "kis_canvas_resource_provider.h" #include "kis_preference_set_registry.h" #include "kis_color_manager.h" #include "KisProofingConfiguration.h" #include "kis_image_config.h" #include "slider_and_spin_box_sync.h" // for the performance update #include #include "input/config/kis_input_configuration_page.h" GeneralTab::GeneralTab(QWidget *_parent, const char *_name) : WdgGeneralSettings(_parent, _name) { KisConfig cfg; m_cmbCursorShape->addItem(i18n("No Cursor")); m_cmbCursorShape->addItem(i18n("Tool Icon")); m_cmbCursorShape->addItem(i18n("Arrow")); m_cmbCursorShape->addItem(i18n("Small Circle")); m_cmbCursorShape->addItem(i18n("Crosshair")); m_cmbCursorShape->addItem(i18n("Triangle Righthanded")); m_cmbCursorShape->addItem(i18n("Triangle Lefthanded")); m_cmbCursorShape->addItem(i18n("Black Pixel")); m_cmbCursorShape->addItem(i18n("White Pixel")); m_cmbOutlineShape->addItem(i18n("No Outline")); m_cmbOutlineShape->addItem(i18n("Circle Outline")); m_cmbOutlineShape->addItem(i18n("Preview Outline")); m_cmbOutlineShape->addItem(i18n("Tilt Outline")); m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle()); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle()); chkShowRootLayer->setChecked(cfg.showRootLayer()); int autosaveInterval = cfg.autoSaveInterval(); //convert to minutes m_autosaveSpinBox->setValue(autosaveInterval / 60); m_autosaveCheckBox->setChecked(autosaveInterval > 0); m_undoStackSize->setValue(cfg.undoStackLimit()); m_backupFileCheckBox->setChecked(cfg.backupFile()); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting()); m_hideSplashScreen->setChecked(cfg.hideSplashScreen()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); m_chkNativeFileDialog->setChecked(!group.readEntry("DontUseNativeFileDialog", true)); intMaxBrushSize->setValue(cfg.readEntry("maximumBrushSize", 1000)); m_cmbMDIType->setCurrentIndex(cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView)); m_chkRubberBand->setChecked(cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets()); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor()); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage()); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages()); m_chkCompressKra->setChecked(cfg.compressKra()); - m_chkHiDPI->setChecked(cfg.readEntry("EnableHiDPI", false)); + + const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); + m_chkHiDPI->setChecked(kritarc.value("EnableHiDPI", false).toBool()); + m_chkSingleApplication->setChecked(kritarc.value("EnableSingleApplication", true).toBool()); + m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker()); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt()); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport()); connect(m_bnFileName, SIGNAL(clicked()), SLOT(getBackgroundImage())); connect(clearBgImageButton, SIGNAL(clicked()), SLOT(clearBackgroundImage())); } void GeneralTab::setDefault() { KisConfig cfg; m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle(true)); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle(true)); chkShowRootLayer->setChecked(cfg.showRootLayer(true)); m_autosaveCheckBox->setChecked(cfg.autoSaveInterval(true) > 0); //convert to minutes m_autosaveSpinBox->setValue(cfg.autoSaveInterval(true) / 60); m_undoStackSize->setValue(cfg.undoStackLimit(true)); m_backupFileCheckBox->setChecked(cfg.backupFile(true)); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting(true)); m_hideSplashScreen->setChecked(cfg.hideSplashScreen(true)); m_chkNativeFileDialog->setChecked(false); intMaxBrushSize->setValue(1000); m_cmbMDIType->setCurrentIndex((int)QMdiArea::TabbedView); m_chkRubberBand->setChecked(cfg.useOpenGL(true)); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets(true)); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor(true)); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage(true)); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages(true)); m_chkCompressKra->setChecked(cfg.compressKra(true)); + m_chkHiDPI->setChecked(false); + m_chkSingleApplication->setChecked(true); + m_chkHiDPI->setChecked(true); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker(true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt(true)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(true)); } CursorStyle GeneralTab::cursorStyle() { return (CursorStyle)m_cmbCursorShape->currentIndex(); } OutlineStyle GeneralTab::outlineStyle() { return (OutlineStyle)m_cmbOutlineShape->currentIndex(); } bool GeneralTab::showRootLayer() { return chkShowRootLayer->isChecked(); } int GeneralTab::autoSaveInterval() { //convert to seconds return m_autosaveCheckBox->isChecked() ? m_autosaveSpinBox->value() * 60 : 0; } int GeneralTab::undoStackSize() { return m_undoStackSize->value(); } bool GeneralTab::showOutlineWhilePainting() { return m_showOutlinePainting->isChecked(); } bool GeneralTab::hideSplashScreen() { return m_hideSplashScreen->isChecked(); } int GeneralTab::mdiMode() { return m_cmbMDIType->currentIndex(); } int GeneralTab::favoritePresets() { return m_favoritePresetsSpinBox->value(); } bool GeneralTab::showCanvasMessages() { return m_chkCanvasMessages->isChecked(); } bool GeneralTab::compressKra() { return m_chkCompressKra->isChecked(); } bool GeneralTab::toolOptionsInDocker() { return m_radioToolOptionsInDocker->isChecked(); } bool GeneralTab::switchSelectionCtrlAlt() { return m_chkSwitchSelectionCtrlAlt->isChecked(); } bool GeneralTab::convertToImageColorspaceOnImport() { return m_chkConvertOnImport->isChecked(); } void GeneralTab::getBackgroundImage() { KoFileDialog dialog(this, KoFileDialog::OpenFile, "BackgroundImages"); dialog.setCaption(i18n("Select a Background Image")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setImageFilters(); QString fn = dialog.filename(); // dialog box was canceled or somehow no file was selected if (fn.isEmpty()) { return; } QImage image(fn); if (image.isNull()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("%1 is not a valid image file!", fn)); } else { m_backgroundimage->setText(fn); } } void GeneralTab::clearBackgroundImage() { // clearing the background image text will implicitly make the background color be used m_backgroundimage->setText(""); } #include "kactioncollection.h" #include "KisActionsSnapshot.h" ShortcutSettingsTab::ShortcutSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgShortcutSettings(this); l->addWidget(m_page, 0, 0); m_snapshot.reset(new KisActionsSnapshot); KActionCollection *collection = KisPart::instance()->currentMainwindow()->actionCollection(); Q_FOREACH (QAction *action, collection->actions()) { m_snapshot->addAction(action->objectName(), action); } QMap sortedCollections = m_snapshot->actionCollections(); for (auto it = sortedCollections.constBegin(); it != sortedCollections.constEnd(); ++it) { m_page->addCollection(it.value(), it.key()); } } ShortcutSettingsTab::~ShortcutSettingsTab() { } void ShortcutSettingsTab::setDefault() { m_page->allDefault(); } void ShortcutSettingsTab::saveChanges() { m_page->save(); KisActionRegistry::instance()->settingsPageSaved(); } void ShortcutSettingsTab::cancelChanges() { m_page->undo(); } ColorSettingsTab::ColorSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); // XXX: Make sure only profiles that fit the specified color model // are shown in the profile combos QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgColorSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg; m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile()); connect(m_page->chkUseSystemMonitorProfile, SIGNAL(toggled(bool)), this, SLOT(toggleAllowMonitorProfileSelection(bool))); m_page->cmbWorkingColorSpace->setIDList(KoColorSpaceRegistry::instance()->listKeys()); m_page->cmbWorkingColorSpace->setCurrent(cfg.workingColorSpace()); m_page->bnAddColorProfile->setIcon(KisIconUtils::loadIcon("document-open")); m_page->bnAddColorProfile->setToolTip( i18n("Open Color Profile") ); connect(m_page->bnAddColorProfile, SIGNAL(clicked()), SLOT(installProfile())); QFormLayout *monitorProfileGrid = new QFormLayout(m_page->monitorprofileholder); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { QLabel *lbl = new QLabel(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileLabels << lbl; SqueezedComboBox *cmb = new SqueezedComboBox(); cmb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); monitorProfileGrid->addRow(lbl, cmb); m_monitorProfileWidgets << cmb; } refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation()); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization()); KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); m_page->sldAdaptationState->setMaximum(20); m_page->sldAdaptationState->setMinimum(0); m_page->sldAdaptationState->setValue((int)proofingConfig->adaptationState*20); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingConfig->proofingProfile); m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_pasteBehaviourGroup.addButton(m_page->radioPasteWeb, PASTE_ASSUME_WEB); m_pasteBehaviourGroup.addButton(m_page->radioPasteMonitor, PASTE_ASSUME_MONITOR); m_pasteBehaviourGroup.addButton(m_page->radioPasteAsk, PASTE_ASK); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour()); Q_ASSERT(button); if (button) { button->setChecked(true); } m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent()); toggleAllowMonitorProfileSelection(cfg.useSystemMonitorProfile()); } void ColorSettingsTab::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { if (!QFile::copy(profileName, saveLocation + QFileInfo(profileName).fileName())) { qWarning() << "Could not install profile!" << saveLocation + QFileInfo(profileName).fileName(); continue; } iccEngine->addProfile(saveLocation + QFileInfo(profileName).fileName()); } KisConfig cfg; refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } void ColorSettingsTab::toggleAllowMonitorProfileSelection(bool useSystemProfile) { if (useSystemProfile) { KisConfig cfg; QStringList devices = KisColorManager::instance()->devices(); if (devices.size() == QApplication::desktop()->screenCount()) { for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); QString monitorForScreen = cfg.monitorForScreen(i, devices[i]); Q_FOREACH (const QString &device, devices) { m_monitorProfileLabels[i]->setText(i18nc("The display/screen we got from Qt", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->addSqueezedItem(KisColorManager::instance()->deviceName(device), device); if (devices[i] == monitorForScreen) { m_monitorProfileWidgets[i]->setCurrentIndex(i); } } } } } else { KisConfig cfg; refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } } void ColorSettingsTab::setDefault() { m_page->cmbWorkingColorSpace->setCurrent("RGBA"); refillMonitorProfiles(KoID("RGBA", "")); KisConfig cfg; KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,proofingConfig->proofingDepth,proofingConfig->proofingProfile); m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_page->sldAdaptationState->setValue(0); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation(true)); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization(true)); m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent(true)); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile(true)); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour(true)); Q_ASSERT(button); if (button) { button->setChecked(true); } } void ColorSettingsTab::refillMonitorProfiles(const KoID & s) { const KoColorSpaceFactory * csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(s.id()); for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); } if (!csf) return; QMap profileList; Q_FOREACH(const KoColorProfile *profile, KoColorSpaceRegistry::instance()->profilesFor(csf)) { profileList[profile->name()] = profile; } Q_FOREACH (const KoColorProfile *profile, profileList.values()) { //qDebug() << "Profile" << profile->name() << profile->isSuitableForDisplay() << csf->defaultProfile(); if (profile->isSuitableForDisplay()) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->addSqueezedItem(profile->name()); } } } for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileLabels[i]->setText(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->setCurrent(csf->defaultProfile()); } } //--------------------------------------------------------------------------------------------------- void TabletSettingsTab::setDefault() { KisCubicCurve curve; curve.fromString(DEFAULT_CURVE_STRING); m_page->pressureCurve->setCurve(curve); } TabletSettingsTab::TabletSettingsTab(QWidget* parent, const char* name): QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgTabletSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg; KisCubicCurve curve; curve.fromString( cfg.pressureTabletCurve() ); m_page->pressureCurve->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); m_page->pressureCurve->setCurve(curve); } //--------------------------------------------------------------------------------------------------- #include "kis_acyclic_signal_connector.h" int getTotalRAM() { KisImageConfig cfg; return cfg.totalRAM(); } int PerformanceTab::realTilesRAM() { return intMemoryLimit->value() - intPoolLimit->value(); } PerformanceTab::PerformanceTab(QWidget *parent, const char *name) : WdgPerformanceSettings(parent, name) { KisImageConfig cfg; const int totalRAM = cfg.totalRAM(); lblTotalMemory->setText(i18n("%1 MiB", totalRAM)); sliderMemoryLimit->setSuffix(i18n(" %")); sliderMemoryLimit->setRange(1, 100, 2); sliderMemoryLimit->setSingleStep(0.01); sliderPoolLimit->setSuffix(i18n(" %")); sliderPoolLimit->setRange(0, 20, 2); sliderMemoryLimit->setSingleStep(0.01); sliderUndoLimit->setSuffix(i18n(" %")); sliderUndoLimit->setRange(0, 50, 2); sliderMemoryLimit->setSingleStep(0.01); intMemoryLimit->setMinimumWidth(80); intPoolLimit->setMinimumWidth(80); intUndoLimit->setMinimumWidth(80); SliderAndSpinBoxSync *sync1 = new SliderAndSpinBoxSync(sliderMemoryLimit, intMemoryLimit, getTotalRAM); sync1->slotParentValueChanged(); m_syncs << sync1; SliderAndSpinBoxSync *sync2 = new SliderAndSpinBoxSync(sliderPoolLimit, intPoolLimit, std::bind(&KisIntParseSpinBox::value, intMemoryLimit)); connect(intMemoryLimit, SIGNAL(valueChanged(int)), sync2, SLOT(slotParentValueChanged())); sync2->slotParentValueChanged(); m_syncs << sync2; SliderAndSpinBoxSync *sync3 = new SliderAndSpinBoxSync(sliderUndoLimit, intUndoLimit, std::bind(&PerformanceTab::realTilesRAM, this)); connect(intPoolLimit, SIGNAL(valueChanged(int)), sync3, SLOT(slotParentValueChanged())); sync3->slotParentValueChanged(); m_syncs << sync3; sliderSwapSize->setSuffix(i18n(" GiB")); sliderSwapSize->setRange(1, 64); intSwapSize->setRange(1, 64); KisAcyclicSignalConnector *swapSizeConnector = new KisAcyclicSignalConnector(this); swapSizeConnector->connectForwardInt(sliderSwapSize, SIGNAL(valueChanged(int)), intSwapSize, SLOT(setValue(int))); swapSizeConnector->connectBackwardInt(intSwapSize, SIGNAL(valueChanged(int)), sliderSwapSize, SLOT(setValue(int))); lblSwapFileLocation->setText(cfg.swapDir()); connect(bnSwapFile, SIGNAL(clicked()), SLOT(selectSwapDir())); load(false); } PerformanceTab::~PerformanceTab() { qDeleteAll(m_syncs); } void PerformanceTab::load(bool requestDefault) { KisImageConfig cfg; sliderMemoryLimit->setValue(cfg.memoryHardLimitPercent(requestDefault)); sliderPoolLimit->setValue(cfg.memoryPoolLimitPercent(requestDefault)); sliderUndoLimit->setValue(cfg.memorySoftLimitPercent(requestDefault)); chkPerformanceLogging->setChecked(cfg.enablePerfLog(requestDefault)); chkProgressReporting->setChecked(cfg.enableProgressReporting(requestDefault)); sliderSwapSize->setValue(cfg.maxSwapSize(requestDefault) / 1024); lblSwapFileLocation->setText(cfg.swapDir(requestDefault)); { KisConfig cfg2; chkOpenGLLogging->setChecked(cfg2.enableOpenGLDebugging(requestDefault)); chkDisableVectorOptimizations->setChecked(cfg2.enableAmdVectorizationWorkaround(requestDefault)); } } void PerformanceTab::save() { KisImageConfig cfg; cfg.setMemoryHardLimitPercent(sliderMemoryLimit->value()); cfg.setMemorySoftLimitPercent(sliderUndoLimit->value()); cfg.setMemoryPoolLimitPercent(sliderPoolLimit->value()); cfg.setEnablePerfLog(chkPerformanceLogging->isChecked()); cfg.setEnableProgressReporting(chkProgressReporting->isChecked()); cfg.setMaxSwapSize(sliderSwapSize->value() * 1024); cfg.setSwapDir(lblSwapFileLocation->text()); { KisConfig cfg2; cfg2.setEnableOpenGLDebugging(chkOpenGLLogging->isChecked()); cfg2.setEnableAmdVectorizationWorkaround(chkDisableVectorOptimizations->isChecked()); } } void PerformanceTab::selectSwapDir() { KisImageConfig cfg; QString swapDir = cfg.swapDir(); swapDir = QFileDialog::getExistingDirectory(0, i18nc("@title:window", "Select a swap directory"), swapDir); lblSwapFileLocation->setText(swapDir); } //--------------------------------------------------------------------------------------------------- #include "KoColor.h" DisplaySettingsTab::DisplaySettingsTab(QWidget *parent, const char *name) : WdgDisplaySettings(parent, name) { KisConfig cfg; if (!KisOpenGL::hasOpenGL()) { grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL()); chkUseTextureBuffer->setEnabled(cfg.useOpenGL()); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer()); chkDisableVsync->setVisible(cfg.showAdvancedOpenGLSettings()); chkDisableVsync->setEnabled(cfg.useOpenGL()); chkDisableVsync->setChecked(cfg.disableVSync()); cmbFilterMode->setEnabled(cfg.useOpenGL()); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode()); // Don't show the high quality filtering mode if it's not available if (!KisOpenGL::supportsLoD()) { cmbFilterMode->removeItem(3); } } if (qApp->applicationName() == "kritasketch" || qApp->applicationName() == "kritagemini") { grpOpenGL->setVisible(false); grpOpenGL->setMaximumHeight(0); } KoColor c; c.fromQColor(cfg.selectionOverlayMaskColor()); c.setOpacity(1.0); btnSelectionOverlayColor->setColor(c); sldSelectionOverlayOpacity->setRange(0.0, 1.0, 2); sldSelectionOverlayOpacity->setSingleStep(0.05); sldSelectionOverlayOpacity->setValue(cfg.selectionOverlayMaskColor().alphaF()); intCheckSize->setValue(cfg.checkSize()); chkMoving->setChecked(cfg.scrollCheckers()); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1()); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2()); colorChecks2->setColor(ck2); KoColor cb(KoColorSpaceRegistry::instance()->rgb8()); cb.fromQColor(cfg.canvasBorderColor()); canvasBorder->setColor(cb); hideScrollbars->setChecked(cfg.hideScrollbars()); chkCurveAntialiasing->setChecked(cfg.antialiasCurves()); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline()); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor()); chkHidePopups->setChecked(cfg.hidePopups()); connect(grpOpenGL, SIGNAL(toggled(bool)), SLOT(slotUseOpenGLToggled(bool))); } void DisplaySettingsTab::setDefault() { KisConfig cfg; if (!KisOpenGL::hasOpenGL()) { grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL(true)); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer(true)); chkUseTextureBuffer->setEnabled(true); chkDisableVsync->setEnabled(true); chkDisableVsync->setChecked(cfg.disableVSync(true)); cmbFilterMode->setEnabled(true); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode(true)); } chkMoving->setChecked(cfg.scrollCheckers(true)); intCheckSize->setValue(cfg.checkSize(true)); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1(true)); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2(true)); colorChecks2->setColor(ck2); KoColor cvb(KoColorSpaceRegistry::instance()->rgb8()); cvb.fromQColor(cfg.canvasBorderColor(true)); canvasBorder->setColor(cvb); hideScrollbars->setChecked(cfg.hideScrollbars(true)); chkCurveAntialiasing->setChecked(cfg.antialiasCurves(true)); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline(true)); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor(true)); chkHidePopups->setChecked(cfg.hidePopups(true)); } void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked) { chkUseTextureBuffer->setEnabled(isChecked); chkDisableVsync->setEnabled(isChecked); cmbFilterMode->setEnabled(isChecked); } //--------------------------------------------------------------------------------------------------- FullscreenSettingsTab::FullscreenSettingsTab(QWidget* parent) : WdgFullscreenSettingsBase(parent) { KisConfig cfg; chkDockers->setChecked(cfg.hideDockersFullscreen()); chkMenu->setChecked(cfg.hideMenuFullscreen()); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen()); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen()); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen()); chkToolbar->setChecked(cfg.hideToolbarFullscreen()); } void FullscreenSettingsTab::setDefault() { KisConfig cfg; chkDockers->setChecked(cfg.hideDockersFullscreen(true)); chkMenu->setChecked(cfg.hideMenuFullscreen(true)); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen(true)); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen(true)); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen(true)); chkToolbar->setChecked(cfg.hideToolbarFullscreen(true)); } //--------------------------------------------------------------------------------------------------- KisDlgPreferences::KisDlgPreferences(QWidget* parent, const char* name) : KPageDialog(parent) { Q_UNUSED(name); setWindowTitle(i18n("Configure Krita")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); button(QDialogButtonBox::Ok)->setDefault(true); setFaceType(KPageDialog::List); // General KoVBox *vbox = new KoVBox(); KPageWidgetItem *page = new KPageWidgetItem(vbox, i18n("General")); page->setObjectName("general"); page->setHeader(i18n("General")); page->setIcon(KisIconUtils::loadIcon("go-home")); addPage(page); m_general = new GeneralTab(vbox); // Shortcuts vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Keyboard Shortcuts")); page->setObjectName("shortcuts"); page->setHeader(i18n("Shortcuts")); page->setIcon(KisIconUtils::loadIcon("document-export")); addPage(page); m_shortcutSettings = new ShortcutSettingsTab(vbox); connect(this, SIGNAL(accepted()), m_shortcutSettings, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_shortcutSettings, SLOT(cancelChanges())); // Canvas input settings m_inputConfiguration = new KisInputConfigurationPage(); page = addPage(m_inputConfiguration, i18n("Canvas Input Settings")); page->setHeader(i18n("Canvas Input")); page->setObjectName("canvasinput"); page->setIcon(KisIconUtils::loadIcon("configure")); // Display vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Display")); page->setObjectName("display"); page->setHeader(i18n("Display")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-display")); addPage(page); m_displaySettings = new DisplaySettingsTab(vbox); // Color vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Color Management")); page->setObjectName("colormanagement"); page->setHeader(i18n("Color")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-color")); addPage(page); m_colorSettings = new ColorSettingsTab(vbox); // Performance vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Performance")); page->setObjectName("performance"); page->setHeader(i18n("Performance")); page->setIcon(KisIconUtils::loadIcon("applications-system")); addPage(page); m_performanceSettings = new PerformanceTab(vbox); // Tablet vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Tablet settings")); page->setObjectName("tablet"); page->setHeader(i18n("Tablet")); page->setIcon(KisIconUtils::loadIcon("document-edit")); addPage(page); m_tabletSettings = new TabletSettingsTab(vbox); // full-screen mode vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Canvas-only settings")); page->setObjectName("canvasonly"); page->setHeader(i18n("Canvas-only")); page->setIcon(KisIconUtils::loadIcon("folder-pictures")); addPage(page); m_fullscreenSettings = new FullscreenSettingsTab(vbox); // Author profiles m_authorPage = new KoConfigAuthorPage(); page = addPage(m_authorPage, i18nc("@title:tab Author page", "Author" )); page->setObjectName("author"); page->setHeader(i18n("Author")); page->setIcon(KisIconUtils::loadIcon("im-user")); QPushButton *restoreDefaultsButton = button(QDialogButtonBox::RestoreDefaults); connect(this, SIGNAL(accepted()), m_inputConfiguration, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_inputConfiguration, SLOT(revertChanges())); KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); Q_FOREACH (KisAbstractPreferenceSetFactory *preferenceSetFactory, preferenceSetRegistry->values()) { KisPreferenceSet* preferenceSet = preferenceSetFactory->createPreferenceSet(); vbox = new KoVBox(); page = new KPageWidgetItem(vbox, preferenceSet->name()); page->setHeader(preferenceSet->header()); page->setIcon(preferenceSet->icon()); addPage(page); preferenceSet->setParent(vbox); preferenceSet->loadPreferences(); connect(restoreDefaultsButton, SIGNAL(clicked(bool)), preferenceSet, SLOT(loadDefaultPreferences()), Qt::UniqueConnection); connect(this, SIGNAL(accepted()), preferenceSet, SLOT(savePreferences()), Qt::UniqueConnection); } connect(restoreDefaultsButton, SIGNAL(clicked(bool)), this, SLOT(slotDefault())); } KisDlgPreferences::~KisDlgPreferences() { } void KisDlgPreferences::slotDefault() { if (currentPage()->objectName() == "general") { m_general->setDefault(); } else if (currentPage()->objectName() == "shortcuts") { m_shortcutSettings->setDefault(); } else if (currentPage()->objectName() == "display") { m_displaySettings->setDefault(); } else if (currentPage()->objectName() == "colormanagement") { m_colorSettings->setDefault(); } else if (currentPage()->objectName() == "performance") { m_performanceSettings->load(true); } else if (currentPage()->objectName() == "tablet") { m_tabletSettings->setDefault(); } else if (currentPage()->objectName() == "canvasonly") { m_fullscreenSettings->setDefault(); } else if (currentPage()->objectName() == "canvasinput") { m_inputConfiguration->setDefaults(); } } bool KisDlgPreferences::editPreferences() { KisDlgPreferences* dialog; dialog = new KisDlgPreferences(); bool baccept = (dialog->exec() == Accepted); if (baccept) { // General settings KisConfig cfg; cfg.setNewCursorStyle(dialog->m_general->cursorStyle()); cfg.setNewOutlineStyle(dialog->m_general->outlineStyle()); cfg.setShowRootLayer(dialog->m_general->showRootLayer()); cfg.setShowOutlineWhilePainting(dialog->m_general->showOutlineWhilePainting()); cfg.setHideSplashScreen(dialog->m_general->hideSplashScreen()); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); group.writeEntry("DontUseNativeFileDialog", !dialog->m_general->m_chkNativeFileDialog->isChecked()); cfg.writeEntry("maximumBrushSize", dialog->m_general->intMaxBrushSize->value()); cfg.writeEntry("mdi_viewmode", dialog->m_general->mdiMode()); cfg.setMDIBackgroundColor(dialog->m_general->m_mdiColor->color().toQColor()); cfg.setMDIBackgroundImage(dialog->m_general->m_backgroundimage->text()); cfg.setAutoSaveInterval(dialog->m_general->autoSaveInterval()); cfg.setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked()); cfg.setShowCanvasMessages(dialog->m_general->showCanvasMessages()); cfg.setCompressKra(dialog->m_general->compressKra()); - cfg.writeEntry("EnableHiDPI", dialog->m_general->m_chkHiDPI->isChecked()); + + const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); + kritarc.setValue("EnableHiDPI", dialog->m_general->m_chkHiDPI->isChecked()); + kritarc.setValue("EnableSingleApplication", dialog->m_general->m_chkSingleApplication->isChecked()); + cfg.setToolOptionsInDocker(dialog->m_general->toolOptionsInDocker()); cfg.setSwitchSelectionCtrlAlt(dialog->m_general->switchSelectionCtrlAlt()); cfg.setConvertToImageColorspaceOnImport(dialog->m_general->convertToImageColorspaceOnImport()); KisPart *part = KisPart::instance(); if (part) { Q_FOREACH (QPointer doc, part->documents()) { if (doc) { doc->setAutoSaveDelay(dialog->m_general->autoSaveInterval()); doc->setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked()); doc->undoStack()->setUndoLimit(dialog->m_general->undoStackSize()); } } } cfg.setUndoStackLimit(dialog->m_general->undoStackSize()); cfg.setFavoritePresets(dialog->m_general->favoritePresets()); // Color settings cfg.setUseSystemMonitorProfile(dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()) { int currentIndex = dialog->m_colorSettings->m_monitorProfileWidgets[i]->currentIndex(); QString monitorid = dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemData(currentIndex).toString(); cfg.setMonitorForScreen(i, monitorid); } else { cfg.setMonitorProfile(i, dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemHighlighted(), dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); } } cfg.setWorkingColorSpace(dialog->m_colorSettings->m_page->cmbWorkingColorSpace->currentItem().id()); KisImageConfig cfgImage; cfgImage.setDefaultProofingConfig(dialog->m_colorSettings->m_page->proofingSpaceSelector->currentColorSpace(), dialog->m_colorSettings->m_page->cmbProofingIntent->currentIndex(), dialog->m_colorSettings->m_page->ckbProofBlackPoint->isChecked(), dialog->m_colorSettings->m_page->gamutAlarm->color(), (double)dialog->m_colorSettings->m_page->sldAdaptationState->value()/20); cfg.setUseBlackPointCompensation(dialog->m_colorSettings->m_page->chkBlackpoint->isChecked()); cfg.setAllowLCMSOptimization(dialog->m_colorSettings->m_page->chkAllowLCMSOptimization->isChecked()); cfg.setPasteBehaviour(dialog->m_colorSettings->m_pasteBehaviourGroup.checkedId()); cfg.setRenderIntent(dialog->m_colorSettings->m_page->cmbMonitorIntent->currentIndex()); // Tablet settings cfg.setPressureTabletCurve( dialog->m_tabletSettings->m_page->pressureCurve->curve().toString() ); dialog->m_performanceSettings->save(); if (!cfg.useOpenGL() && dialog->m_displaySettings->grpOpenGL->isChecked()) cfg.setCanvasState("TRY_OPENGL"); cfg.setUseOpenGL(dialog->m_displaySettings->grpOpenGL->isChecked()); cfg.setUseOpenGLTextureBuffer(dialog->m_displaySettings->chkUseTextureBuffer->isChecked()); cfg.setOpenGLFilteringMode(dialog->m_displaySettings->cmbFilterMode->currentIndex()); cfg.setDisableVSync(dialog->m_displaySettings->chkDisableVsync->isChecked()); cfg.setCheckSize(dialog->m_displaySettings->intCheckSize->value()); cfg.setScrollingCheckers(dialog->m_displaySettings->chkMoving->isChecked()); cfg.setCheckersColor1(dialog->m_displaySettings->colorChecks1->color().toQColor()); cfg.setCheckersColor2(dialog->m_displaySettings->colorChecks2->color().toQColor()); cfg.setCanvasBorderColor(dialog->m_displaySettings->canvasBorder->color().toQColor()); cfg.setHideScrollbars(dialog->m_displaySettings->hideScrollbars->isChecked()); KoColor c = dialog->m_displaySettings->btnSelectionOverlayColor->color(); c.setOpacity(dialog->m_displaySettings->sldSelectionOverlayOpacity->value()); cfg.setSelectionOverlayMaskColor(c.toQColor()); cfg.setAntialiasCurves(dialog->m_displaySettings->chkCurveAntialiasing->isChecked()); cfg.setAntialiasSelectionOutline(dialog->m_displaySettings->chkSelectionOutlineAntialiasing->isChecked()); cfg.setShowSingleChannelAsColor(dialog->m_displaySettings->chkChannelsAsColor->isChecked()); cfg.setHidePopups(dialog->m_displaySettings->chkHidePopups->isChecked()); cfg.setHideDockersFullscreen(dialog->m_fullscreenSettings->chkDockers->checkState()); cfg.setHideMenuFullscreen(dialog->m_fullscreenSettings->chkMenu->checkState()); cfg.setHideScrollbarsFullscreen(dialog->m_fullscreenSettings->chkScrollbars->checkState()); cfg.setHideStatusbarFullscreen(dialog->m_fullscreenSettings->chkStatusbar->checkState()); cfg.setHideTitlebarFullscreen(dialog->m_fullscreenSettings->chkTitlebar->checkState()); cfg.setHideToolbarFullscreen(dialog->m_fullscreenSettings->chkToolbar->checkState()); dialog->m_authorPage->apply(); } delete dialog; return baccept; } diff --git a/libs/ui/forms/wdggeneralsettings.ui b/libs/ui/forms/wdggeneralsettings.ui index f475a196b3..9a32182829 100644 --- a/libs/ui/forms/wdggeneralsettings.ui +++ b/libs/ui/forms/wdggeneralsettings.ui @@ -1,669 +1,683 @@ WdgGeneralSettings 0 0 759 468 0 0 552 295 Qt::LeftToRight 0 Cursor 10 10 10 10 10 10 0 0 Cursor Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Outline Shape: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 200 0 Show brush outline while painting Qt::Horizontal 40 20 Qt::Vertical 20 40 Window 0 0 Multiple Document Mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 1 Subwindows Tabs Background Image (overrides color): 200 0 QFrame::StyledPanel QFrame::Sunken ... 0 0 Clear Window Background: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Don't show contents when moving sub-windows: 0 0 Show on-canvas popup messages: - + Qt::Vertical 20 40 Enable Hi-DPI support: + + + + + + + + + + + Allow only one instance of Krita: + + + Tools 10 10 10 10 10 Tool Options Location (needs restart) In Doc&ker In Tool&bar true Switch Control/Alt Selection Modifiers Qt::Vertical 20 40 Qt::Horizontal 40 20 Miscellaneous 0 0 Qt::RightToLeft Autosave every: true 0 0 75 0 min 1 1440 5 15 Compress .kra files more (slows loading/saving) Create backup file On importing images as layers, convert to the image colorspace 0 0 Undo stack size: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 75 0 0 1000 5 30 0 0 Number of Palette Presets Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 75 0 10 30 Show root layer Hide splash screen on startup Warning: if you enable this setting and the file dialogs do weird stuff, do not report a bug. Enable native file dialogs (warning: may not work correctly on some systems) Maximum brush size: 0 0 The maximum diameter of a brush in pixels. px 100 10000 1000 (Needs restart) Qt::Vertical 504 13 KisColorButton QPushButton
kis_color_button.h
KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc index ccca8f328a..5d053bd040 100644 --- a/libs/ui/kis_config.cc +++ b/libs/ui/kis_config.cc @@ -1,1818 +1,1822 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_config.h" #include #include #include #include #include #include #include #include - +#include +#include #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "kis_config_notifier.h" #include "kis_snap_config.h" #include #include KisConfig::KisConfig() : m_cfg( KSharedConfig::openConfig()->group("")) { } KisConfig::~KisConfig() { if (qApp->thread() != QThread::currentThread()) { //dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Skipping..."; return; } m_cfg.sync(); } bool KisConfig::disableTouchOnCanvas(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableTouchOnCanvas", false)); } void KisConfig::setDisableTouchOnCanvas(bool value) const { m_cfg.writeEntry("disableTouchOnCanvas", value); } bool KisConfig::useProjections(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useProjections", true)); } void KisConfig::setUseProjections(bool useProj) const { m_cfg.writeEntry("useProjections", useProj); } bool KisConfig::undoEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("undoEnabled", true)); } void KisConfig::setUndoEnabled(bool undo) const { m_cfg.writeEntry("undoEnabled", undo); } int KisConfig::undoStackLimit(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("undoStackLimit", 30)); } void KisConfig::setUndoStackLimit(int limit) const { m_cfg.writeEntry("undoStackLimit", limit); } bool KisConfig::useCumulativeUndoRedo(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useCumulativeUndoRedo",false)); } void KisConfig::setCumulativeUndoRedo(bool value) { m_cfg.writeEntry("useCumulativeUndoRedo", value); } qreal KisConfig::stackT1(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackT1",5)); } void KisConfig::setStackT1(int T1) { m_cfg.writeEntry("stackT1", T1); } qreal KisConfig::stackT2(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("stackT2",1)); } void KisConfig::setStackT2(int T2) { m_cfg.writeEntry("stackT2", T2); } int KisConfig::stackN(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackN",5)); } void KisConfig::setStackN(int N) { m_cfg.writeEntry("stackN", N); } qint32 KisConfig::defImageWidth(bool defaultValue) const { return (defaultValue ? 1600 : m_cfg.readEntry("imageWidthDef", 1600)); } qint32 KisConfig::defImageHeight(bool defaultValue) const { return (defaultValue ? 1200 : m_cfg.readEntry("imageHeightDef", 1200)); } qreal KisConfig::defImageResolution(bool defaultValue) const { return (defaultValue ? 100.0 : m_cfg.readEntry("imageResolutionDef", 100.0)) / 72.0; } QString KisConfig::defColorModel(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id() : m_cfg.readEntry("colorModelDef", KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id())); } void KisConfig::defColorModel(const QString & model) const { m_cfg.writeEntry("colorModelDef", model); } QString KisConfig::defaultColorDepth(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id() : m_cfg.readEntry("colorDepthDef", KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id())); } void KisConfig::setDefaultColorDepth(const QString & depth) const { m_cfg.writeEntry("colorDepthDef", depth); } QString KisConfig::defColorProfile(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->profile()->name() : m_cfg.readEntry("colorProfileDef", KoColorSpaceRegistry::instance()->rgb8()->profile()->name())); } void KisConfig::defColorProfile(const QString & profile) const { m_cfg.writeEntry("colorProfileDef", profile); } void KisConfig::defImageWidth(qint32 width) const { m_cfg.writeEntry("imageWidthDef", width); } void KisConfig::defImageHeight(qint32 height) const { m_cfg.writeEntry("imageHeightDef", height); } void KisConfig::defImageResolution(qreal res) const { m_cfg.writeEntry("imageResolutionDef", res*72.0); } void cleanOldCursorStyleKeys(KConfigGroup &cfg) { if (cfg.hasKey("newCursorStyle") && cfg.hasKey("newOutlineStyle")) { cfg.deleteEntry("cursorStyleDef"); } } CursorStyle KisConfig::newCursorStyle(bool defaultValue) const { if (defaultValue) { return CURSOR_STYLE_NO_CURSOR; } int style = m_cfg.readEntry("newCursorStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: style = CURSOR_STYLE_TOOLICON; break; case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: style = CURSOR_STYLE_CROSSHAIR; break; case OLD_CURSOR_STYLE_POINTER: style = CURSOR_STYLE_POINTER; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_NO_CURSOR: style = CURSOR_STYLE_NO_CURSOR; break; case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: style = CURSOR_STYLE_SMALL_ROUND; break; case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: style = CURSOR_STYLE_TRIANGLE_RIGHTHANDED; break; case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = CURSOR_STYLE_TRIANGLE_LEFTHANDED; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_CURSOR_STYLE_SIZE) { style = CURSOR_STYLE_NO_CURSOR; } return (CursorStyle) style; } void KisConfig::setNewCursorStyle(CursorStyle style) { m_cfg.writeEntry("newCursorStyle", (int)style); } OutlineStyle KisConfig::newOutlineStyle(bool defaultValue) const { if (defaultValue) { return OUTLINE_FULL; } int style = m_cfg.readEntry("newOutlineStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_POINTER: case OLD_CURSOR_STYLE_NO_CURSOR: case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: style = OUTLINE_NONE; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = OUTLINE_FULL; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_OUTLINE_STYLE_SIZE) { style = OUTLINE_FULL; } return (OutlineStyle) style; } void KisConfig::setNewOutlineStyle(OutlineStyle style) { m_cfg.writeEntry("newOutlineStyle", (int)style); } QRect KisConfig::colorPreviewRect() const { return m_cfg.readEntry("colorPreviewRect", QVariant(QRect(32, 32, 48, 48))).toRect(); } void KisConfig::setColorPreviewRect(const QRect &rect) { m_cfg.writeEntry("colorPreviewRect", QVariant(rect)); } bool KisConfig::useDirtyPresets(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useDirtyPresets",false)); } void KisConfig::setUseDirtyPresets(bool value) { m_cfg.writeEntry("useDirtyPresets",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushSize(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushSize",false)); } void KisConfig::setUseEraserBrushSize(bool value) { m_cfg.writeEntry("useEraserBrushSize",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushOpacity(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushOpacity",false)); } void KisConfig::setUseEraserBrushOpacity(bool value) { m_cfg.writeEntry("useEraserBrushOpacity",value); KisConfigNotifier::instance()->notifyConfigChanged(); } QColor KisConfig::getMDIBackgroundColor(bool defaultValue) const { QColor col(77, 77, 77); return (defaultValue ? col : m_cfg.readEntry("mdiBackgroundColor", col)); } void KisConfig::setMDIBackgroundColor(const QColor &v) const { m_cfg.writeEntry("mdiBackgroundColor", v); } QString KisConfig::getMDIBackgroundImage(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("mdiBackgroundImage", "")); } void KisConfig::setMDIBackgroundImage(const QString &filename) const { m_cfg.writeEntry("mdiBackgroundImage", filename); } QString KisConfig::monitorProfile(int screen) const { // Note: keep this in sync with the default profile for the RGB colorspaces! QString profile = m_cfg.readEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), "sRGB-elle-V2-srgbtrc.icc"); //dbgKrita << "KisConfig::monitorProfile()" << profile; return profile; } QString KisConfig::monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue) const { return (defaultValue ? defaultMonitor : m_cfg.readEntry(QString("monitor_for_screen_%1").arg(screen), defaultMonitor)); } void KisConfig::setMonitorForScreen(int screen, const QString& monitor) { m_cfg.writeEntry(QString("monitor_for_screen_%1").arg(screen), monitor); } void KisConfig::setMonitorProfile(int screen, const QString & monitorProfile, bool override) const { m_cfg.writeEntry("monitorProfile/OverrideX11", override); m_cfg.writeEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), monitorProfile); } const KoColorProfile *KisConfig::getScreenProfile(int screen) { if (screen < 0) return 0; KisConfig cfg; QString monitorId; if (KisColorManager::instance()->devices().size() > screen) { monitorId = cfg.monitorForScreen(screen, KisColorManager::instance()->devices()[screen]); } //dbgKrita << "getScreenProfile(). Screen" << screen << "monitor id" << monitorId; if (monitorId.isEmpty()) { return 0; } QByteArray bytes = KisColorManager::instance()->displayProfile(monitorId); //dbgKrita << "\tgetScreenProfile()" << bytes.size(); if (bytes.length() > 0) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), bytes); //dbgKrita << "\tKisConfig::getScreenProfile for screen" << screen << profile->name(); return profile; } else { //dbgKrita << "\tCould not get a system monitor profile"; return 0; } } const KoColorProfile *KisConfig::displayProfile(int screen) const { if (screen < 0) return 0; // if the user plays with the settings, they can override the display profile, in which case // we don't want the system setting. bool override = useSystemMonitorProfile(); //dbgKrita << "KisConfig::displayProfile(). Override X11:" << override; const KoColorProfile *profile = 0; if (override) { //dbgKrita << "\tGoing to get the screen profile"; profile = KisConfig::getScreenProfile(screen); } // if it fails. check the configuration if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tGoing to get the monitor profile"; QString monitorProfileName = monitorProfile(screen); //dbgKrita << "\t\tmonitorProfileName:" << monitorProfileName; if (!monitorProfileName.isEmpty()) { profile = KoColorSpaceRegistry::instance()->profileByName(monitorProfileName); } if (profile) { //dbgKrita << "\t\tsuitable for display" << profile->isSuitableForDisplay(); } else { //dbgKrita << "\t\tstill no profile"; } } // if we still don't have a profile, or the profile isn't suitable for display, // we need to get a last-resort profile. the built-in sRGB is a good choice then. if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tnothing worked, going to get sRGB built-in"; profile = KoColorSpaceRegistry::instance()->profileByName("sRGB Built-in"); } if (profile) { //dbgKrita << "\tKisConfig::displayProfile for screen" << screen << "is" << profile->name(); } else { //dbgKrita << "\tCouldn't get a display profile at all"; } return profile; } QString KisConfig::workingColorSpace(bool defaultValue) const { return (defaultValue ? "RGBA" : m_cfg.readEntry("workingColorSpace", "RGBA")); } void KisConfig::setWorkingColorSpace(const QString & workingColorSpace) const { m_cfg.writeEntry("workingColorSpace", workingColorSpace); } QString KisConfig::printerColorSpace(bool /*defaultValue*/) const { //TODO currently only rgb8 is supported //return (defaultValue ? "RGBA" : m_cfg.readEntry("printerColorSpace", "RGBA")); return QString("RGBA"); } void KisConfig::setPrinterColorSpace(const QString & printerColorSpace) const { m_cfg.writeEntry("printerColorSpace", printerColorSpace); } QString KisConfig::printerProfile(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("printerProfile", "")); } void KisConfig::setPrinterProfile(const QString & printerProfile) const { m_cfg.writeEntry("printerProfile", printerProfile); } bool KisConfig::useBlackPointCompensation(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useBlackPointCompensation", true)); } void KisConfig::setUseBlackPointCompensation(bool useBlackPointCompensation) const { m_cfg.writeEntry("useBlackPointCompensation", useBlackPointCompensation); } bool KisConfig::allowLCMSOptimization(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("allowLCMSOptimization", true)); } void KisConfig::setAllowLCMSOptimization(bool allowLCMSOptimization) { m_cfg.writeEntry("allowLCMSOptimization", allowLCMSOptimization); } bool KisConfig::showRulers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showrulers", false)); } void KisConfig::setShowRulers(bool rulers) const { m_cfg.writeEntry("showrulers", rulers); } bool KisConfig::rulersTrackMouse(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("rulersTrackMouse", true)); } void KisConfig::setRulersTrackMouse(bool value) const { m_cfg.writeEntry("rulersTrackMouse", value); } qint32 KisConfig::pasteBehaviour(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("pasteBehaviour", 2)); } void KisConfig::setPasteBehaviour(qint32 renderIntent) const { m_cfg.writeEntry("pasteBehaviour", renderIntent); } qint32 KisConfig::monitorRenderIntent(bool defaultValue) const { qint32 intent = m_cfg.readEntry("renderIntent", INTENT_PERCEPTUAL); if (intent > 3) intent = 3; if (intent < 0) intent = 0; return (defaultValue ? INTENT_PERCEPTUAL : intent); } void KisConfig::setRenderIntent(qint32 renderIntent) const { if (renderIntent > 3) renderIntent = 3; if (renderIntent < 0) renderIntent = 0; m_cfg.writeEntry("renderIntent", renderIntent); } bool KisConfig::useOpenGL(bool defaultValue) const { if (defaultValue) { return true; } //dbgKrita << "use opengl" << m_cfg.readEntry("useOpenGL", true) << "success" << m_cfg.readEntry("canvasState", "OPENGL_SUCCESS"); QString cs = canvasState(); return (m_cfg.readEntry("useOpenGL", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL")); } void KisConfig::setUseOpenGL(bool useOpenGL) const { m_cfg.writeEntry("useOpenGL", useOpenGL); } int KisConfig::openGLFilteringMode(bool defaultValue) const { return (defaultValue ? 3 : m_cfg.readEntry("OpenGLFilterMode", 3)); } void KisConfig::setOpenGLFilteringMode(int filteringMode) { m_cfg.writeEntry("OpenGLFilterMode", filteringMode); } bool KisConfig::useOpenGLTextureBuffer(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useOpenGLTextureBuffer", true)); } void KisConfig::setUseOpenGLTextureBuffer(bool useBuffer) { m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer); } int KisConfig::openGLTextureSize(bool defaultValue) const { return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256)); } bool KisConfig::disableVSync(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("disableVSync", true)); } void KisConfig::setDisableVSync(bool disableVSync) { m_cfg.writeEntry("disableVSync", disableVSync); } bool KisConfig::showAdvancedOpenGLSettings(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showAdvancedOpenGLSettings", false)); } bool KisConfig::forceOpenGLFenceWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceOpenGLFenceWorkaround", false)); } int KisConfig::numMipmapLevels(bool defaultValue) const { return (defaultValue ? 4 : m_cfg.readEntry("numMipmapLevels", 4)); } int KisConfig::textureOverlapBorder() const { return 1 << qMax(0, numMipmapLevels()); } qint32 KisConfig::maxNumberOfThreads(bool defaultValue) const { return (defaultValue ? QThread::idealThreadCount() : m_cfg.readEntry("maxthreads", QThread::idealThreadCount())); } void KisConfig::setMaxNumberOfThreads(qint32 maxThreads) { m_cfg.writeEntry("maxthreads", maxThreads); } quint32 KisConfig::getGridMainStyle(bool defaultValue) const { int v = m_cfg.readEntry("gridmainstyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGridMainStyle(quint32 v) const { m_cfg.writeEntry("gridmainstyle", v); } quint32 KisConfig::getGridSubdivisionStyle(bool defaultValue) const { quint32 v = m_cfg.readEntry("gridsubdivisionstyle", 1); if (v > 2) v = 2; return (defaultValue ? 1 : v); } void KisConfig::setGridSubdivisionStyle(quint32 v) const { m_cfg.writeEntry("gridsubdivisionstyle", v); } QColor KisConfig::getGridMainColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("gridmaincolor", col)); } void KisConfig::setGridMainColor(const QColor & v) const { m_cfg.writeEntry("gridmaincolor", v); } QColor KisConfig::getGridSubdivisionColor(bool defaultValue) const { QColor col(150, 150, 150); return (defaultValue ? col : m_cfg.readEntry("gridsubdivisioncolor", col)); } void KisConfig::setGridSubdivisionColor(const QColor & v) const { m_cfg.writeEntry("gridsubdivisioncolor", v); } quint32 KisConfig::guidesLineStyle(bool defaultValue) const { int v = m_cfg.readEntry("guidesLineStyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGuidesLineStyle(quint32 v) const { m_cfg.writeEntry("guidesLineStyle", v); } QColor KisConfig::guidesColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("guidesColor", col)); } void KisConfig::setGuidesColor(const QColor & v) const { m_cfg.writeEntry("guidesColor", v); } void KisConfig::loadSnapConfig(KisSnapConfig *config, bool defaultValue) const { KisSnapConfig defaultConfig(false); if (defaultValue) { *config = defaultConfig; return; } config->setOrthogonal(m_cfg.readEntry("globalSnapOrthogonal", defaultConfig.orthogonal())); config->setNode(m_cfg.readEntry("globalSnapNode", defaultConfig.node())); config->setExtension(m_cfg.readEntry("globalSnapExtension", defaultConfig.extension())); config->setIntersection(m_cfg.readEntry("globalSnapIntersection", defaultConfig.intersection())); config->setBoundingBox(m_cfg.readEntry("globalSnapBoundingBox", defaultConfig.boundingBox())); config->setImageBounds(m_cfg.readEntry("globalSnapImageBounds", defaultConfig.imageBounds())); config->setImageCenter(m_cfg.readEntry("globalSnapImageCenter", defaultConfig.imageCenter())); } void KisConfig::saveSnapConfig(const KisSnapConfig &config) { m_cfg.writeEntry("globalSnapOrthogonal", config.orthogonal()); m_cfg.writeEntry("globalSnapNode", config.node()); m_cfg.writeEntry("globalSnapExtension", config.extension()); m_cfg.writeEntry("globalSnapIntersection", config.intersection()); m_cfg.writeEntry("globalSnapBoundingBox", config.boundingBox()); m_cfg.writeEntry("globalSnapImageBounds", config.imageBounds()); m_cfg.writeEntry("globalSnapImageCenter", config.imageCenter()); } qint32 KisConfig::checkSize(bool defaultValue) const { return (defaultValue ? 32 : m_cfg.readEntry("checksize", 32)); } void KisConfig::setCheckSize(qint32 checksize) const { m_cfg.writeEntry("checksize", checksize); } bool KisConfig::scrollCheckers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("scrollingcheckers", false)); } void KisConfig::setScrollingCheckers(bool sc) const { m_cfg.writeEntry("scrollingcheckers", sc); } QColor KisConfig::canvasBorderColor(bool defaultValue) const { QColor color(QColor(128,128,128)); return (defaultValue ? color : m_cfg.readEntry("canvasBorderColor", color)); } void KisConfig::setCanvasBorderColor(const QColor& color) const { m_cfg.writeEntry("canvasBorderColor", color); } bool KisConfig::hideScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hideScrollbars", false)); } void KisConfig::setHideScrollbars(bool value) const { m_cfg.writeEntry("hideScrollbars", value); } QColor KisConfig::checkersColor1(bool defaultValue) const { QColor col(220, 220, 220); return (defaultValue ? col : m_cfg.readEntry("checkerscolor", col)); } void KisConfig::setCheckersColor1(const QColor & v) const { m_cfg.writeEntry("checkerscolor", v); } QColor KisConfig::checkersColor2(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("checkerscolor2", QColor(Qt::white))); } void KisConfig::setCheckersColor2(const QColor & v) const { m_cfg.writeEntry("checkerscolor2", v); } bool KisConfig::antialiasCurves(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("antialiascurves", true)); } void KisConfig::setAntialiasCurves(bool v) const { m_cfg.writeEntry("antialiascurves", v); } QColor KisConfig::selectionOverlayMaskColor(bool defaultValue) const { QColor def(255, 0, 0, 220); return (defaultValue ? def : m_cfg.readEntry("selectionOverlayMaskColor", def)); } void KisConfig::setSelectionOverlayMaskColor(const QColor &color) { m_cfg.writeEntry("selectionOverlayMaskColor", color); } bool KisConfig::antialiasSelectionOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("AntialiasSelectionOutline", false)); } void KisConfig::setAntialiasSelectionOutline(bool v) const { m_cfg.writeEntry("AntialiasSelectionOutline", v); } bool KisConfig::showRootLayer(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowRootLayer", false)); } void KisConfig::setShowRootLayer(bool showRootLayer) const { m_cfg.writeEntry("ShowRootLayer", showRootLayer); } bool KisConfig::showGlobalSelection(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowGlobalSelection", false)); } void KisConfig::setShowGlobalSelection(bool showGlobalSelection) const { m_cfg.writeEntry("ShowGlobalSelection", showGlobalSelection); } bool KisConfig::showOutlineWhilePainting(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ShowOutlineWhilePainting", true)); } void KisConfig::setShowOutlineWhilePainting(bool showOutlineWhilePainting) const { m_cfg.writeEntry("ShowOutlineWhilePainting", showOutlineWhilePainting); } bool KisConfig::hideSplashScreen(bool defaultValue) const { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); return (defaultValue ? true : cfg.readEntry("HideSplashAfterStartup", true)); } void KisConfig::setHideSplashScreen(bool hideSplashScreen) const { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); cfg.writeEntry("HideSplashAfterStartup", hideSplashScreen); } qreal KisConfig::outlineSizeMinimum(bool defaultValue) const { return (defaultValue ? 1.0 : m_cfg.readEntry("OutlineSizeMinimum", 1.0)); } void KisConfig::setOutlineSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("OutlineSizeMinimum", outlineSizeMinimum); } qreal KisConfig::selectionViewSizeMinimum(bool defaultValue) const { return (defaultValue ? 5.0 : m_cfg.readEntry("SelectionViewSizeMinimum", 5.0)); } void KisConfig::setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("SelectionViewSizeMinimum", outlineSizeMinimum); } int KisConfig::autoSaveInterval(bool defaultValue) const { return (defaultValue ? 15 * 60 : m_cfg.readEntry("AutoSaveInterval", 15 * 60)); } void KisConfig::setAutoSaveInterval(int seconds) const { return m_cfg.writeEntry("AutoSaveInterval", seconds); } bool KisConfig::backupFile(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("CreateBackupFile", true)); } void KisConfig::setBackupFile(bool backupFile) const { m_cfg.writeEntry("CreateBackupFile", backupFile); } bool KisConfig::showFilterGallery(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showFilterGallery", false)); } void KisConfig::setShowFilterGallery(bool showFilterGallery) const { m_cfg.writeEntry("showFilterGallery", showFilterGallery); } bool KisConfig::showFilterGalleryLayerMaskDialog(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showFilterGalleryLayerMaskDialog", true)); } void KisConfig::setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const { m_cfg.writeEntry("setShowFilterGalleryLayerMaskDialog", showFilterGallery); } QString KisConfig::canvasState(bool defaultValue) const { - return (defaultValue ? "OPENGL_NOT_TRIED" : m_cfg.readEntry("canvasState", "OPENGL_NOT_TRIED")); + const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); + return (defaultValue ? "OPENGL_NOT_TRIED" : kritarc.value("canvasState", "OPENGL_NOT_TRIED").toString()); } void KisConfig::setCanvasState(const QString& state) const { static QStringList acceptableStates; if (acceptableStates.isEmpty()) { acceptableStates << "OPENGL_SUCCESS" << "TRY_OPENGL" << "OPENGL_NOT_TRIED" << "OPENGL_FAILED"; } if (acceptableStates.contains(state)) { - m_cfg.writeEntry("canvasState", state); - m_cfg.sync(); + const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); + kritarc.setValue("canvasState", state); } } bool KisConfig::toolOptionsPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ToolOptionsPopupDetached", false)); } void KisConfig::setToolOptionsPopupDetached(bool detached) const { m_cfg.writeEntry("ToolOptionsPopupDetached", detached); } bool KisConfig::paintopPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("PaintopPopupDetached", false)); } void KisConfig::setPaintopPopupDetached(bool detached) const { m_cfg.writeEntry("PaintopPopupDetached", detached); } QString KisConfig::pressureTabletCurve(bool defaultValue) const { return (defaultValue ? "0,0;1,1" : m_cfg.readEntry("tabletPressureCurve","0,0;1,1;")); } void KisConfig::setPressureTabletCurve(const QString& curveString) const { m_cfg.writeEntry("tabletPressureCurve", curveString); } qreal KisConfig::vastScrolling(bool defaultValue) const { return (defaultValue ? 0.9 : m_cfg.readEntry("vastScrolling", 0.9)); } void KisConfig::setVastScrolling(const qreal factor) const { m_cfg.writeEntry("vastScrolling", factor); } int KisConfig::presetChooserViewMode(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("presetChooserViewMode", 0)); } void KisConfig::setPresetChooserViewMode(const int mode) const { m_cfg.writeEntry("presetChooserViewMode", mode); } int KisConfig::presetIconSize(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("presetIconSize", 30)); } void KisConfig::setPresetIconSize(const int value) const { m_cfg.writeEntry("presetIconSize", value); } bool KisConfig::firstRun(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("firstRun", true)); } void KisConfig::setFirstRun(const bool first) const { m_cfg.writeEntry("firstRun", first); } int KisConfig::horizontalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("horizontalSplitLines", 1)); } void KisConfig::setHorizontalSplitLines(const int numberLines) const { m_cfg.writeEntry("horizontalSplitLines", numberLines); } int KisConfig::verticalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("verticalSplitLines", 1)); } void KisConfig::setVerticalSplitLines(const int numberLines) const { m_cfg.writeEntry("verticalSplitLines", numberLines); } bool KisConfig::clicklessSpacePan(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("clicklessSpacePan", true)); } void KisConfig::setClicklessSpacePan(const bool toggle) const { m_cfg.writeEntry("clicklessSpacePan", toggle); } bool KisConfig::hideDockersFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideDockersFullScreen", true)); } void KisConfig::setHideDockersFullscreen(const bool value) const { m_cfg.writeEntry("hideDockersFullScreen", value); } bool KisConfig::showDockerTitleBars(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showDockerTitleBars", true)); } void KisConfig::setShowDockerTitleBars(const bool value) const { m_cfg.writeEntry("showDockerTitleBars", value); } bool KisConfig::showDockers(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showDockers", true)); } void KisConfig::setShowDockers(const bool value) const { m_cfg.writeEntry("showDockers", value); } bool KisConfig::showStatusBar(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showStatusBar", true)); } void KisConfig::setShowStatusBar(const bool value) const { m_cfg.writeEntry("showStatusBar", value); } bool KisConfig::hideMenuFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideMenuFullScreen", true)); } void KisConfig::setHideMenuFullscreen(const bool value) const { m_cfg.writeEntry("hideMenuFullScreen", value); } bool KisConfig::hideScrollbarsFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideScrollbarsFullScreen", true)); } void KisConfig::setHideScrollbarsFullscreen(const bool value) const { m_cfg.writeEntry("hideScrollbarsFullScreen", value); } bool KisConfig::hideStatusbarFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideStatusbarFullScreen", true)); } void KisConfig::setHideStatusbarFullscreen(const bool value) const { m_cfg.writeEntry("hideStatusbarFullScreen", value); } bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true)); } void KisConfig::setHideTitlebarFullscreen(const bool value) const { m_cfg.writeEntry("hideTitleBarFullscreen", value); } bool KisConfig::hideToolbarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideToolbarFullscreen", true)); } void KisConfig::setHideToolbarFullscreen(const bool value) const { m_cfg.writeEntry("hideToolbarFullscreen", value); } bool KisConfig::fullscreenMode(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("fullscreenMode", true)); } void KisConfig::setFullscreenMode(const bool value) const { m_cfg.writeEntry("fullscreenMode", value); } QStringList KisConfig::favoriteCompositeOps(bool defaultValue) const { return (defaultValue ? QStringList() : m_cfg.readEntry("favoriteCompositeOps", QStringList())); } void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const { m_cfg.writeEntry("favoriteCompositeOps", compositeOps); } QString KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString())); } void KisConfig::setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString exportConfig = properties->toXML(); m_cfg.writeEntry("ExportConfiguration-" + filterId, exportConfig); } bool KisConfig::useOcio(bool defaultValue) const { #ifdef HAVE_OCIO return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/UseOcio", false)); #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseOcio(bool useOCIO) const { m_cfg.writeEntry("Krita/Ocio/UseOcio", useOCIO); } int KisConfig::favoritePresets(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("numFavoritePresets", 10)); } void KisConfig::setFavoritePresets(const int value) { m_cfg.writeEntry("numFavoritePresets", value); } bool KisConfig::levelOfDetailEnabled(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("levelOfDetailEnabled", false)); } void KisConfig::setLevelOfDetailEnabled(bool value) { m_cfg.writeEntry("levelOfDetailEnabled", value); } KisConfig::OcioColorManagementMode KisConfig::ocioColorManagementMode(bool defaultValue) const { return (OcioColorManagementMode)(defaultValue ? INTERNAL : m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL)); } void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const { m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode); } QString KisConfig::ocioConfigurationPath(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString())); } void KisConfig::setOcioConfigurationPath(const QString &path) const { m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", path); } QString KisConfig::ocioLutPath(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString())); } void KisConfig::setOcioLutPath(const QString &path) const { m_cfg.writeEntry("Krita/Ocio/OcioLutPath", path); } int KisConfig::ocioLutEdgeSize(bool defaultValue) const { return (defaultValue ? 64 : m_cfg.readEntry("Krita/Ocio/LutEdgeSize", 64)); } void KisConfig::setOcioLutEdgeSize(int value) { m_cfg.writeEntry("Krita/Ocio/LutEdgeSize", value); } bool KisConfig::ocioLockColorVisualRepresentation(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/OcioLockColorVisualRepresentation", false)); } void KisConfig::setOcioLockColorVisualRepresentation(bool value) { m_cfg.writeEntry("Krita/Ocio/OcioLockColorVisualRepresentation", value); } QString KisConfig::defaultPalette(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("defaultPalette", QString())); } void KisConfig::setDefaultPalette(const QString& name) const { m_cfg.writeEntry("defaultPalette", name); } QString KisConfig::toolbarSlider(int sliderNumber, bool defaultValue) const { QString def = "flow"; if (sliderNumber == 1) { def = "opacity"; } if (sliderNumber == 2) { def = "size"; } return (defaultValue ? def : m_cfg.readEntry(QString("toolbarslider_%1").arg(sliderNumber), def)); } void KisConfig::setToolbarSlider(int sliderNumber, const QString &slider) { m_cfg.writeEntry(QString("toolbarslider_%1").arg(sliderNumber), slider); } bool KisConfig::sliderLabels(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("sliderLabels", true)); } void KisConfig::setSliderLabels(bool enabled) { m_cfg.writeEntry("sliderLabels", enabled); } QString KisConfig::currentInputProfile(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("currentInputProfile", QString())); } void KisConfig::setCurrentInputProfile(const QString& name) { m_cfg.writeEntry("currentInputProfile", name); } bool KisConfig::useSystemMonitorProfile(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ColorManagement/UseSystemMonitorProfile", false)); } void KisConfig::setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const { m_cfg.writeEntry("ColorManagement/UseSystemMonitorProfile", _useSystemMonitorProfile); } bool KisConfig::presetStripVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("presetStripVisible", true)); } void KisConfig::setPresetStripVisible(bool visible) { m_cfg.writeEntry("presetStripVisible", visible); } bool KisConfig::scratchpadVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("scratchpadVisible", true)); } void KisConfig::setScratchpadVisible(bool visible) { m_cfg.writeEntry("scratchpadVisible", visible); } bool KisConfig::showSingleChannelAsColor(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showSingleChannelAsColor", false)); } void KisConfig::setShowSingleChannelAsColor(bool asColor) { m_cfg.writeEntry("showSingleChannelAsColor", asColor); } bool KisConfig::hidePopups(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hidePopups", false)); } void KisConfig::setHidePopups(bool hidepopups) { m_cfg.writeEntry("hidePopups", hidepopups); } int KisConfig::numDefaultLayers(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("NumberOfLayersForNewImage", 2)); } void KisConfig::setNumDefaultLayers(int num) { m_cfg.writeEntry("NumberOfLayersForNewImage", num); } quint8 KisConfig::defaultBackgroundOpacity(bool defaultValue) const { return (defaultValue ? (int)OPACITY_OPAQUE_U8 : m_cfg.readEntry("BackgroundOpacityForNewImage", (int)OPACITY_OPAQUE_U8)); } void KisConfig::setDefaultBackgroundOpacity(quint8 value) { m_cfg.writeEntry("BackgroundOpacityForNewImage", (int)value); } QColor KisConfig::defaultBackgroundColor(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("BackgroundColorForNewImage", QColor(Qt::white))); } void KisConfig::setDefaultBackgroundColor(QColor value) { m_cfg.writeEntry("BackgroundColorForNewImage", value); } KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const { return (KisConfig::BackgroundStyle)(defaultValue ? LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)LAYER)); } void KisConfig::setDefaultBackgroundStyle(KisConfig::BackgroundStyle value) { m_cfg.writeEntry("BackgroundStyleForNewImage", (int)value); } int KisConfig::lineSmoothingType(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("LineSmoothingType", 1)); } void KisConfig::setLineSmoothingType(int value) { m_cfg.writeEntry("LineSmoothingType", value); } qreal KisConfig::lineSmoothingDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDistance", 50.0)); } void KisConfig::setLineSmoothingDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDistance", value); } qreal KisConfig::lineSmoothingTailAggressiveness(bool defaultValue) const { return (defaultValue ? 0.15 : m_cfg.readEntry("LineSmoothingTailAggressiveness", 0.15)); } void KisConfig::setLineSmoothingTailAggressiveness(qreal value) { m_cfg.writeEntry("LineSmoothingTailAggressiveness", value); } bool KisConfig::lineSmoothingSmoothPressure(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("LineSmoothingSmoothPressure", false)); } void KisConfig::setLineSmoothingSmoothPressure(bool value) { m_cfg.writeEntry("LineSmoothingSmoothPressure", value); } bool KisConfig::lineSmoothingScalableDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingScalableDistance", true)); } void KisConfig::setLineSmoothingScalableDistance(bool value) { m_cfg.writeEntry("LineSmoothingScalableDistance", value); } qreal KisConfig::lineSmoothingDelayDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDelayDistance", 50.0)); } void KisConfig::setLineSmoothingDelayDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDelayDistance", value); } bool KisConfig::lineSmoothingUseDelayDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingUseDelayDistance", true)); } void KisConfig::setLineSmoothingUseDelayDistance(bool value) { m_cfg.writeEntry("LineSmoothingUseDelayDistance", value); } bool KisConfig::lineSmoothingFinishStabilizedCurve(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true)); } void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value) { m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value); } bool KisConfig::lineSmoothingStabilizeSensors(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingStabilizeSensors", true)); } void KisConfig::setLineSmoothingStabilizeSensors(bool value) { m_cfg.writeEntry("LineSmoothingStabilizeSensors", value); } int KisConfig::paletteDockerPaletteViewSectionSize(bool defaultValue) const { return (defaultValue ? 12 : m_cfg.readEntry("paletteDockerPaletteViewSectionSize", 12)); } void KisConfig::setPaletteDockerPaletteViewSectionSize(int value) const { m_cfg.writeEntry("paletteDockerPaletteViewSectionSize", value); } int KisConfig::tabletEventsDelay(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("tabletEventsDelay", 10)); } void KisConfig::setTabletEventsDelay(int value) { m_cfg.writeEntry("tabletEventsDelay", value); } bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false)); } void KisConfig::setTestingAcceptCompressedTabletEvents(bool value) { m_cfg.writeEntry("testingAcceptCompressedTabletEvents", value); } bool KisConfig::shouldEatDriverShortcuts(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("shouldEatDriverShortcuts", false)); } bool KisConfig::testingCompressBrushEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingCompressBrushEvents", false)); } void KisConfig::setTestingCompressBrushEvents(bool value) { m_cfg.writeEntry("testingCompressBrushEvents", value); } bool KisConfig::useVerboseOpenGLDebugOutput(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useVerboseOpenGLDebugOutput", false)); } int KisConfig::workaroundX11SmoothPressureSteps(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("workaroundX11SmoothPressureSteps", 0)); } bool KisConfig::showCanvasMessages(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showOnCanvasMessages", true)); } void KisConfig::setShowCanvasMessages(bool show) { m_cfg.writeEntry("showOnCanvasMessages", show); } bool KisConfig::compressKra(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("compressLayersInKra", false)); } void KisConfig::setCompressKra(bool compress) { m_cfg.writeEntry("compressLayersInKra", compress); } bool KisConfig::toolOptionsInDocker(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ToolOptionsInDocker", true)); } void KisConfig::setToolOptionsInDocker(bool inDocker) { m_cfg.writeEntry("ToolOptionsInDocker", inDocker); } const KoColorSpace* KisConfig::customColorSelectorColorSpace(bool defaultValue) const { const KoColorSpace *cs = 0; KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); if (defaultValue || cfg.readEntry("useCustomColorSpace", true)) { KoColorSpaceRegistry* csr = KoColorSpaceRegistry::instance(); QString modelID = cfg.readEntry("customColorSpaceModel", "RGBA"); QString depthID = cfg.readEntry("customColorSpaceDepthID", "U8"); QString profile = cfg.readEntry("customColorSpaceProfile", "sRGB built-in - (lcms internal)"); if (profile == "default") { // qDebug() << "Falling back to default color profile."; profile = "sRGB built-in - (lcms internal)"; } cs = csr->colorSpace(modelID, depthID, profile); } return cs; } void KisConfig::setCustomColorSelectorColorSpace(const KoColorSpace *cs) { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); cfg.writeEntry("useCustomColorSpace", bool(cs)); if(cs) { cfg.writeEntry("customColorSpaceModel", cs->colorModelId().id()); cfg.writeEntry("customColorSpaceDepthID", cs->colorDepthId().id()); cfg.writeEntry("customColorSpaceProfile", cs->profile()->name()); } KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::enableOpenGLDebugging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableOpenGLDebugging", false)); } void KisConfig::setEnableOpenGLDebugging(bool value) const { m_cfg.writeEntry("enableOpenGLDebugging", value); } void KisConfig::setEnableAmdVectorizationWorkaround(bool value) { m_cfg.writeEntry("amdDisableVectorWorkaround", value); } bool KisConfig::enableAmdVectorizationWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("amdDisableVectorWorkaround", false)); } void KisConfig::setAnimationDropFrames(bool value) { bool oldValue = animationDropFrames(); if (value == oldValue) return; m_cfg.writeEntry("animationDropFrames", value); KisConfigNotifier::instance()->notifyDropFramesModeChanged(); } bool KisConfig::animationDropFrames(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("animationDropFrames", true)); } int KisConfig::scrubbingUpdatesDelay(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("scrubbingUpdatesDelay", 30)); } void KisConfig::setScrubbingUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingUpdatesDelay", value); } int KisConfig::scrubbingAudioUpdatesDelay(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("scrubbingAudioUpdatesDelay", -1)); } void KisConfig::setScrubbingAudioUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingAudioUpdatesDelay", value); } int KisConfig::audioOffsetTolerance(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("audioOffsetTolerance", -1)); } void KisConfig::setAudioOffsetTolerance(int value) { m_cfg.writeEntry("audioOffsetTolerance", value); } bool KisConfig::switchSelectionCtrlAlt(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("switchSelectionCtrlAlt", false); } void KisConfig::setSwitchSelectionCtrlAlt(bool value) { m_cfg.writeEntry("switchSelectionCtrlAlt", value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::convertToImageColorspaceOnImport(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("ConvertToImageColorSpaceOnImport", false); } void KisConfig::setConvertToImageColorspaceOnImport(bool value) { m_cfg.writeEntry("ConvertToImageColorSpaceOnImport", value); } int KisConfig::stabilizerSampleSize(bool defaultValue) const { #ifdef Q_OS_WIN const int defaultSampleSize = 50; #else const int defaultSampleSize = 15; #endif return defaultValue ? defaultSampleSize : m_cfg.readEntry("stabilizerSampleSize", defaultSampleSize); } void KisConfig::setStabilizerSampleSize(int value) { m_cfg.writeEntry("stabilizerSampleSize", value); } int KisConfig::stabilizerDelayedPaintInterval(bool defaultValue) const { const int defaultInterval = 20; return defaultValue ? defaultInterval : m_cfg.readEntry("stabilizerDelayedPaintInterval", defaultInterval); } void KisConfig::setStabilizerDelayedPaintInterval(int value) { m_cfg.writeEntry("stabilizerDelayedPaintInterval", value); } QString KisConfig::customFFMpegPath(bool defaultValue) const { return defaultValue ? QString() : m_cfg.readEntry("ffmpegExecutablePath", QString()); } void KisConfig::setCustomFFMpegPath(const QString &value) const { m_cfg.writeEntry("ffmpegExecutablePath", value); } bool KisConfig::showBrushHud(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("showBrushHud", false); } void KisConfig::setShowBrushHud(bool value) { m_cfg.writeEntry("showBrushHud", value); } QString KisConfig::brushHudSetting(bool defaultValue) const { QString defaultDoc = "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n"; return defaultValue ? defaultDoc : m_cfg.readEntry("brushHudSettings", defaultDoc); } void KisConfig::setBrushHudSetting(const QString &value) const { m_cfg.writeEntry("brushHudSettings", value); } #include #include void KisConfig::writeKoColor(const QString& name, const KoColor& color) const { QDomDocument doc = QDomDocument(name); QDomElement el = doc.createElement(name); doc.appendChild(el); color.toXML(doc, el); m_cfg.writeEntry(name, doc.toString()); } //ported from kispropertiesconfig. KoColor KisConfig::readKoColor(const QString& name, const KoColor& color) const { QDomDocument doc; if (!m_cfg.readEntry(name).isNull()) { doc.setContent(m_cfg.readEntry(name)); QDomElement e = doc.documentElement().firstChild().toElement(); - return KoColor::fromXML(e, Integer16BitsColorDepthID.id(), QHash()); + return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } else { QString blackColor = "\n\n \n\n"; doc.setContent(blackColor); QDomElement e = doc.documentElement().firstChild().toElement(); - return KoColor::fromXML(e, Integer16BitsColorDepthID.id(), QHash()); + return KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } return color; } diff --git a/libs/ui/kis_filter_manager.cc b/libs/ui/kis_filter_manager.cc index 13e2fc6d51..5f19104b76 100644 --- a/libs/ui/kis_filter_manager.cc +++ b/libs/ui/kis_filter_manager.cc @@ -1,350 +1,350 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_filter_manager.h" #include #include #include #include #include #include #include // krita/image #include #include #include #include // krita/ui #include "KisViewManager.h" #include "kis_canvas2.h" #include #include "kis_action.h" #include "kis_action_manager.h" #include "kis_canvas_resource_provider.h" #include "dialogs/kis_dlg_filter.h" #include "strokes/kis_filter_stroke_strategy.h" #include "krita_utils.h" struct KisFilterManager::Private { Private() : reapplyAction(0) , actionCollection(0) , actionManager(0) , view(0) { } KisAction* reapplyAction; QHash filterActionMenus; QHash filters2Action; KActionCollection *actionCollection; KisActionManager *actionManager; KisViewManager *view; KisFilterConfigurationSP lastConfiguration; KisFilterConfigurationSP currentlyAppliedConfiguration; KisStrokeId currentStrokeId; QRect initialApplyRect; QSignalMapper actionsMapper; QPointer filterDialog; }; KisFilterManager::KisFilterManager(KisViewManager * view) : d(new Private) { d->view = view; } KisFilterManager::~KisFilterManager() { delete d; } void KisFilterManager::setView(QPointerimageView) { Q_UNUSED(imageView); } void KisFilterManager::setup(KActionCollection * ac, KisActionManager *actionManager) { d->actionCollection = ac; d->actionManager = actionManager; // Setup reapply action d->reapplyAction = d->actionManager->createAction("filter_apply_again"); d->reapplyAction->setEnabled(false); connect(d->reapplyAction, SIGNAL(triggered()), SLOT(reapplyLastFilter())); connect(&d->actionsMapper, SIGNAL(mapped(const QString&)), SLOT(showFilterDialog(const QString&))); // Setup list of filters QStringList keys = KisFilterRegistry::instance()->keys(); keys.sort(); Q_FOREACH (const QString &filterName, keys) { insertFilter(filterName); } connect(KisFilterRegistry::instance(), SIGNAL(filterAdded(QString)), SLOT(insertFilter(const QString &))); } void KisFilterManager::insertFilter(const QString & filterName) { Q_ASSERT(d->actionCollection); KisFilterSP filter = KisFilterRegistry::instance()->value(filterName); Q_ASSERT(filter); if (d->filters2Action.contains(filter.data())) { warnKrita << "Filter" << filterName << " has already been inserted"; return; } KoID category = filter->menuCategory(); KActionMenu* actionMenu = d->filterActionMenus[ category.id()]; if (!actionMenu) { actionMenu = new KActionMenu(category.name(), this); d->actionCollection->addAction(category.id(), actionMenu); d->filterActionMenus[category.id()] = actionMenu; } KisAction *action = new KisAction(filter->menuEntry(), this); action->setDefaultShortcut(filter->shortcut()); action->setActivationFlags(KisAction::ACTIVE_DEVICE); d->actionManager->addAction(QString("krita_filter_%1").arg(filterName), action); d->filters2Action[filter.data()] = action; actionMenu->addAction(action); d->actionsMapper.setMapping(action, filterName); connect(action, SIGNAL(triggered()), &d->actionsMapper, SLOT(map())); } void KisFilterManager::updateGUI() { if (!d->view) return; bool enable = false; KisNodeSP activeNode = d->view->activeNode(); enable = activeNode && activeNode->hasEditablePaintDevice(); d->reapplyAction->setEnabled(enable); for (QHash::iterator it = d->filters2Action.begin(); it != d->filters2Action.end(); ++it) { bool localEnable = enable; it.value()->setEnabled(localEnable); } } void KisFilterManager::reapplyLastFilter() { if (!d->lastConfiguration) return; apply(d->lastConfiguration); finish(); } void KisFilterManager::showFilterDialog(const QString &filterId) { if (d->filterDialog && d->filterDialog->isVisible()) { KisFilterSP filter = KisFilterRegistry::instance()->value(filterId); d->filterDialog->setFilter(filter); return; } connect(d->view->image(), SIGNAL(sigStrokeCancellationRequested()), SLOT(slotStrokeCancelRequested()), Qt::UniqueConnection); connect(d->view->image(), SIGNAL(sigStrokeEndRequested()), SLOT(slotStrokeEndRequested()), Qt::UniqueConnection); /** * The UI should show only after every running stroke is finished, * so a virtual barrier is added here. */ - if (!d->view->blockUntillOperationsFinished(d->view->image())) { + if (!d->view->blockUntilOperationsFinished(d->view->image())) { return; } Q_ASSERT(d->view); Q_ASSERT(d->view->activeNode()); KisPaintDeviceSP dev = d->view->activeNode()->paintDevice(); if (!dev) { warnKrita << "KisFilterManager::showFilterDialog(): Filtering was requested for illegal active layer!" << d->view->activeNode(); return; } KisFilterSP filter = KisFilterRegistry::instance()->value(filterId); if (dev->colorSpace()->willDegrade(filter->colorSpaceIndependence())) { // Warning bells! if (filter->colorSpaceIndependence() == TO_LAB16) { if (QMessageBox::warning(d->view->mainWindow(), i18nc("@title:window", "Krita"), i18n("The %1 filter will convert your %2 data to 16-bit L*a*b* and vice versa. ", filter->name(), dev->colorSpace()->name()), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok) != QMessageBox::Ok) return; } else if (filter->colorSpaceIndependence() == TO_RGBA16) { if (QMessageBox::warning(d->view->mainWindow(), i18nc("@title:window", "Krita"), i18n("The %1 filter will convert your %2 data to 16-bit RGBA and vice versa. ", filter->name() , dev->colorSpace()->name()), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok) != QMessageBox::Ok) return; } } if (filter->showConfigurationWidget()) { if (!d->filterDialog) { d->filterDialog = new KisDlgFilter(d->view , d->view->activeNode(), this, d->view->mainWindow()); d->filterDialog->setAttribute(Qt::WA_DeleteOnClose); } d->filterDialog->setFilter(filter); d->filterDialog->setVisible(true); } else { apply(KisFilterConfigurationSP(filter->defaultConfiguration())); finish(); } } void KisFilterManager::apply(KisFilterConfigurationSP filterConfig) { KisFilterSP filter = KisFilterRegistry::instance()->value(filterConfig->name()); KisImageWSP image = d->view->image(); if (d->currentStrokeId) { image->addJob(d->currentStrokeId, new KisFilterStrokeStrategy::CancelSilentlyMarker); image->cancelStroke(d->currentStrokeId); d->currentStrokeId.clear(); } else { image->waitForDone(); d->initialApplyRect = d->view->activeNode()->exactBounds(); } QRect applyRect = d->initialApplyRect; KisPaintDeviceSP paintDevice = d->view->activeNode()->paintDevice(); if (paintDevice && filter->needsTransparentPixels(filterConfig.data(), paintDevice->colorSpace())) { applyRect |= image->bounds(); } KoCanvasResourceManager *resourceManager = d->view->resourceProvider()->resourceManager(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, d->view->activeNode(), resourceManager); d->currentStrokeId = image->startStroke(new KisFilterStrokeStrategy(filter, KisFilterConfigurationSP(filterConfig), resources)); QRect processRect = filter->changedRect(applyRect, filterConfig.data(), 0); processRect &= image->bounds(); if (filter->supportsThreading()) { QSize size = KritaUtils::optimalPatchSize(); QVector rects = KritaUtils::splitRectIntoPatches(processRect, size); Q_FOREACH (const QRect &rc, rects) { image->addJob(d->currentStrokeId, new KisFilterStrokeStrategy::Data(rc, true)); } } else { image->addJob(d->currentStrokeId, new KisFilterStrokeStrategy::Data(processRect, false)); } d->currentlyAppliedConfiguration = filterConfig; } void KisFilterManager::finish() { Q_ASSERT(d->currentStrokeId); d->view->image()->endStroke(d->currentStrokeId); KisFilterSP filter = KisFilterRegistry::instance()->value(d->currentlyAppliedConfiguration->name()); if (filter->bookmarkManager()) { filter->bookmarkManager()->save(KisBookmarkedConfigurationManager::ConfigLastUsed, d->currentlyAppliedConfiguration.data()); } d->lastConfiguration = d->currentlyAppliedConfiguration; d->reapplyAction->setEnabled(true); d->reapplyAction->setText(i18n("Apply Filter Again: %1", filter->name())); d->currentStrokeId.clear(); d->currentlyAppliedConfiguration.clear(); } void KisFilterManager::cancel() { Q_ASSERT(d->currentStrokeId); d->view->image()->cancelStroke(d->currentStrokeId); d->currentStrokeId.clear(); d->currentlyAppliedConfiguration.clear(); } bool KisFilterManager::isStrokeRunning() const { return d->currentStrokeId; } void KisFilterManager::slotStrokeEndRequested() { if (d->currentStrokeId && d->filterDialog) { d->filterDialog->accept(); } } void KisFilterManager::slotStrokeCancelRequested() { if (d->currentStrokeId && d->filterDialog) { d->filterDialog->reject(); } } diff --git a/libs/ui/kis_layer_manager.cc b/libs/ui/kis_layer_manager.cc index 8398bdb375..5fbb2b6e5c 100644 --- a/libs/ui/kis_layer_manager.cc +++ b/libs/ui/kis_layer_manager.cc @@ -1,845 +1,821 @@ /* * Copyright (C) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisImportExportManager.h" #include "kis_config.h" #include "kis_cursor.h" #include "dialogs/kis_dlg_adj_layer_props.h" #include "dialogs/kis_dlg_adjustment_layer.h" #include "dialogs/kis_dlg_layer_properties.h" #include "dialogs/kis_dlg_generator_layer.h" #include "dialogs/kis_dlg_file_layer.h" #include "dialogs/kis_dlg_layer_style.h" #include "KisDocument.h" #include "kis_filter_manager.h" #include "kis_node_visitor.h" #include "kis_paint_layer.h" #include "commands/kis_image_commands.h" #include "commands/kis_layer_command.h" #include "commands/kis_node_commands.h" #include "kis_canvas_resource_provider.h" #include "kis_selection_manager.h" #include "kis_statusbar.h" #include "KisViewManager.h" #include "kis_zoom_manager.h" #include "canvas/kis_canvas2.h" #include "widgets/kis_meta_data_merge_strategy_chooser_widget.h" #include "widgets/kis_wdg_generator.h" #include "kis_progress_widget.h" #include "kis_node_commands_adapter.h" #include "kis_node_manager.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisPart.h" #include "kis_raster_keyframe_channel.h" #include "kis_signal_compressor_with_param.h" #include "kis_abstract_projection_plane.h" #include "commands_new/kis_set_layer_style_command.h" #include "kis_post_execution_undo_adapter.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "lazybrush/kis_colorize_mask.h" #include "KisSaveGroupVisitor.h" KisLayerManager::KisLayerManager(KisViewManager * view) : m_view(view) , m_imageView(0) , m_imageFlatten(0) , m_imageMergeLayer(0) , m_groupLayersSave(0) , m_imageResizeToLayer(0) , m_flattenLayer(0) , m_rasterizeLayer(0) , m_commandsAdapter(new KisNodeCommandsAdapter(m_view)) , m_layerStyle(0) { } KisLayerManager::~KisLayerManager() { delete m_commandsAdapter; } void KisLayerManager::setView(QPointerview) { m_imageView = view; } KisLayerSP KisLayerManager::activeLayer() { if (m_imageView) { return m_imageView->currentLayer(); } return 0; } KisPaintDeviceSP KisLayerManager::activeDevice() { if (activeLayer()) { return activeLayer()->paintDevice(); } return 0; } void KisLayerManager::activateLayer(KisLayerSP layer) { if (m_imageView) { emit sigLayerActivated(layer); layersUpdated(); if (layer) { m_view->resourceProvider()->slotNodeActivated(layer.data()); } } } void KisLayerManager::setup(KisActionManager* actionManager) { m_imageFlatten = actionManager->createAction("flatten_image"); connect(m_imageFlatten, SIGNAL(triggered()), this, SLOT(flattenImage())); m_imageMergeLayer = actionManager->createAction("merge_layer"); connect(m_imageMergeLayer, SIGNAL(triggered()), this, SLOT(mergeLayer())); m_flattenLayer = actionManager->createAction("flatten_layer"); connect(m_flattenLayer, SIGNAL(triggered()), this, SLOT(flattenLayer())); m_rasterizeLayer = actionManager->createAction("rasterize_layer"); connect(m_rasterizeLayer, SIGNAL(triggered()), this, SLOT(rasterizeLayer())); m_groupLayersSave = actionManager->createAction("save_groups_as_images"); connect(m_groupLayersSave, SIGNAL(triggered()), this, SLOT(saveGroupLayers())); m_convertGroupAnimated = actionManager->createAction("convert_group_to_animated"); connect(m_convertGroupAnimated, SIGNAL(triggered()), this, SLOT(convertGroupToAnimated())); m_imageResizeToLayer = actionManager->createAction("resizeimagetolayer"); connect(m_imageResizeToLayer, SIGNAL(triggered()), this, SLOT(imageResizeToActiveLayer())); KisAction *action = actionManager->createAction("trim_to_image"); connect(action, SIGNAL(triggered()), this, SLOT(trimToImage())); m_layerStyle = actionManager->createAction("layer_style"); connect(m_layerStyle, SIGNAL(triggered()), this, SLOT(layerStyle())); } void KisLayerManager::updateGUI() { KisImageWSP image = m_view->image(); KisLayerSP layer; qint32 nlayers = 0; if (image) { layer = activeLayer(); nlayers = image->nlayers(); } // XXX these should be named layer instead of image m_imageFlatten->setEnabled(nlayers > 1); m_imageMergeLayer->setEnabled(nlayers > 1 && layer && layer->prevSibling()); m_flattenLayer->setEnabled(nlayers > 1 && layer && layer->firstChild()); if (m_view->statusBar()) m_view->statusBar()->setProfile(image); } void KisLayerManager::imageResizeToActiveLayer() { KisLayerSP layer; KisImageWSP image = m_view->image(); if (image && (layer = activeLayer())) { QRect cropRect = layer->projection()->nonDefaultPixelArea(); if (!cropRect.isEmpty()) { image->cropImage(cropRect); } else { m_view->showFloatingMessage( i18nc("floating message in layer manager", "Layer is empty "), QIcon(), 2000, KisFloatingMessage::Low); } } } void KisLayerManager::trimToImage() { KisImageWSP image = m_view->image(); if (image) { image->cropImage(image->bounds()); } } void KisLayerManager::layerProperties() { if (!m_view) return; if (!m_view->document()) return; KisLayerSP layer = activeLayer(); QList selectedNodes = m_view->nodeManager()->selectedNodes(); const bool multipleLayersSelected = selectedNodes.size() > 1; if (!layer) return; KisAdjustmentLayerSP alayer = KisAdjustmentLayerSP(dynamic_cast(layer.data())); KisGeneratorLayerSP glayer = KisGeneratorLayerSP(dynamic_cast(layer.data())); if (alayer && !multipleLayersSelected) { KisPaintDeviceSP dev = alayer->projection(); KisDlgAdjLayerProps dlg(alayer, alayer.data(), dev, m_view, alayer->filter().data(), alayer->name(), i18n("Filter Layer Properties"), m_view->mainWindow(), "dlgadjlayerprops"); dlg.resize(dlg.minimumSizeHint()); KisFilterConfigurationSP configBefore(alayer->filter()); KIS_ASSERT_RECOVER_RETURN(configBefore); QString xmlBefore = configBefore->toXML(); if (dlg.exec() == QDialog::Accepted) { alayer->setName(dlg.layerName()); KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { KisChangeFilterCmd *cmd = new KisChangeFilterCmd(alayer, configBefore->name(), xmlBefore, configAfter->name(), xmlAfter, false); // FIXME: check whether is needed cmd->redo(); m_view->undoAdapter()->addCommand(cmd); m_view->document()->setModified(true); } } else { KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { alayer->setFilter(KisFilterRegistry::instance()->cloneConfiguration(configBefore.data())); alayer->setDirty(); } } } else if (glayer && !multipleLayersSelected) { KisDlgGeneratorLayer dlg(glayer->name(), m_view, m_view->mainWindow()); dlg.setCaption(i18n("Fill Layer Properties")); KisFilterConfigurationSP configBefore(glayer->filter()); Q_ASSERT(configBefore); QString xmlBefore = configBefore->toXML(); dlg.setConfiguration(configBefore.data()); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { glayer->setName(dlg.layerName()); KisFilterConfigurationSP configAfter(dlg.configuration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { KisChangeFilterCmd *cmd = new KisChangeFilterCmd(glayer, configBefore->name(), xmlBefore, configAfter->name(), xmlAfter, true); // FIXME: check whether is needed cmd->redo(); m_view->undoAdapter()->addCommand(cmd); m_view->document()->setModified(true); } } } else { // If layer == normal painting layer, vector layer, or group layer QList selectedNodes = m_view->nodeManager()->selectedNodes(); KisDlgLayerProperties *dialog = new KisDlgLayerProperties(selectedNodes, m_view); dialog->resize(dialog->minimumSizeHint()); dialog->setAttribute(Qt::WA_DeleteOnClose); Qt::WindowFlags flags = dialog->windowFlags(); dialog->setWindowFlags(flags | Qt::WindowStaysOnTopHint | Qt::Dialog); dialog->show(); } } void KisLayerManager::convertNodeToPaintLayer(KisNodeSP source) { KisImageWSP image = m_view->image(); if (!image) return; KisLayer *srcLayer = qobject_cast(source.data()); if (srcLayer && (srcLayer->inherits("KisGroupLayer") || srcLayer->layerStyle() || srcLayer->childCount() > 0)) { image->flattenLayer(srcLayer); return; } KisPaintDeviceSP srcDevice = source->paintDevice() ? source->projection() : source->original(); bool putBehind = false; QString newCompositeOp = source->compositeOpId(); KisColorizeMask *colorizeMask = dynamic_cast(source.data()); if (colorizeMask) { srcDevice = colorizeMask->coloringProjection(); putBehind = colorizeMask->compositeOpId() == COMPOSITE_BEHIND; if (putBehind) { newCompositeOp = COMPOSITE_OVER; } } if (!srcDevice) return; KisPaintDeviceSP clone; if (*srcDevice->colorSpace() != *srcDevice->compositionSourceColorSpace()) { clone = new KisPaintDevice(srcDevice->compositionSourceColorSpace()); QRect rc(srcDevice->extent()); KisPainter::copyAreaOptimized(rc.topLeft(), srcDevice, clone, rc); } else { clone = new KisPaintDevice(*srcDevice); } KisLayerSP layer = new KisPaintLayer(image, source->name(), source->opacity(), clone); layer->setCompositeOpId(newCompositeOp); KisNodeSP parent = source->parent(); KisNodeSP above = source; while (parent && !parent->allowAsChild(layer)) { above = above->parent(); parent = above ? above->parent() : 0; } if (putBehind && above == source->parent()) { above = above->prevSibling(); } m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a Paint Layer")); m_commandsAdapter->addNode(layer, parent, above); m_commandsAdapter->removeNode(source); m_commandsAdapter->endMacro(); } void KisLayerManager::convertGroupToAnimated() { KisGroupLayerSP group = dynamic_cast(activeLayer().data()); if (group.isNull()) return; KisPaintLayerSP animatedLayer = new KisPaintLayer(m_view->image(), group->name(), OPACITY_OPAQUE_U8); animatedLayer->enableAnimation(); KisRasterKeyframeChannel *contentChannel = dynamic_cast( animatedLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); KIS_ASSERT_RECOVER_RETURN(contentChannel); KisNodeSP child = group->firstChild(); int time = 0; while (child) { contentChannel->importFrame(time, child->projection(), NULL); time++; child = child->nextSibling(); } m_commandsAdapter->beginMacro(kundo2_i18n("Convert to an animated layer")); m_commandsAdapter->addNode(animatedLayer, group->parent(), group); m_commandsAdapter->removeNode(group); m_commandsAdapter->endMacro(); } void KisLayerManager::adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above) { Q_ASSERT(activeNode); parent = activeNode; above = parent->lastChild(); while (parent && (!parent->allowAsChild(node) || parent->userLocked())) { above = parent; parent = parent->parent(); } if (!parent) { warnKrita << "KisLayerManager::adjustLayerPosition:" << "No node accepted newly created node"; parent = m_view->image()->root(); above = parent->lastChild(); } } void KisLayerManager::addLayerCommon(KisNodeSP activeNode, KisLayerSP layer, bool updateImage) { KisNodeSP parent; KisNodeSP above; adjustLayerPosition(layer, activeNode, parent, above); KisGroupLayer *group = dynamic_cast(parent.data()); const bool parentForceUpdate = group && !group->projectionIsValid(); updateImage |= parentForceUpdate; m_commandsAdapter->addNode(layer, parent, above, updateImage, updateImage); } KisLayerSP KisLayerManager::addLayer(KisNodeSP activeNode) { KisLayerSP layer = KisLayerUtils::constructDefaultLayer(m_view->image()); addLayerCommon(activeNode, layer, false); return layer; } void KisLayerManager::addGroupLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); addLayerCommon(activeNode, new KisGroupLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8), false); } void KisLayerManager::addCloneLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); addLayerCommon(activeNode, new KisCloneLayer(activeLayer(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8)); } void KisLayerManager::addShapeLayer(KisNodeSP activeNode) { if (!m_view) return; if (!m_view->document()) return; KisImageWSP image = m_view->image(); KisShapeLayerSP layer = new KisShapeLayer(m_view->document()->shapeController(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, layer, false); } void KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisSelectionSP selection = m_view->selection(); KisAdjustmentLayerSP adjl = addAdjustmentLayer(activeNode, QString(), 0, selection); image->refreshGraph(); KisPaintDeviceSP previewDevice = new KisPaintDevice(*adjl->original()); KisDlgAdjustmentLayer dlg(adjl, adjl.data(), previewDevice, image->nextLayerName(), i18n("New Filter Layer"), m_view); dlg.resize(dlg.minimumSizeHint()); // ensure that the device may be free'd by the dialog // when it is not needed anymore previewDevice = 0; if (dlg.exec() != QDialog::Accepted || adjl->filter().isNull()) { // XXX: add messagebox warning if there's no filter set! m_commandsAdapter->undoLastCommand(); } else { adjl->setName(dlg.layerName()); } } KisAdjustmentLayerSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode, const QString & name, KisFilterConfigurationSP filter, KisSelectionSP selection) { KisImageWSP image = m_view->image(); KisAdjustmentLayerSP layer = new KisAdjustmentLayer(image, name, filter, selection); addLayerCommon(activeNode, layer); return layer; } void KisLayerManager::addGeneratorLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisDlgGeneratorLayer dlg(image->nextLayerName(), m_view, m_view->mainWindow()); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { KisSelectionSP selection = m_view->selection(); KisFilterConfigurationSP generator = dlg.configuration(); QString name = dlg.layerName(); addLayerCommon(activeNode, new KisGeneratorLayer(image, name, generator, selection)); } } -void KisLayerManager::rotateLayer(double radians) -{ - if (!m_view->image()) return; - - KisLayerSP layer = activeLayer(); - if (!layer) return; - - if (!m_view->blockUntillOperationsFinished(m_view->image())) return; - - m_view->image()->rotateNode(layer, radians); -} - -void KisLayerManager::shearLayer(double angleX, double angleY) -{ - if (!m_view->image()) return; - - KisLayerSP layer = activeLayer(); - if (!layer) return; - - if (!m_view->blockUntillOperationsFinished(m_view->image())) return; - - m_view->image()->shearNode(layer, angleX, angleY); -} - void KisLayerManager::flattenImage() { KisImageSP image = m_view->image(); - if (!m_view->blockUntillOperationsFinished(image)) return; + if (!m_view->blockUntilOperationsFinished(image)) return; if (image) { bool doIt = true; if (image->nHiddenLayers() > 0) { int answer = QMessageBox::warning(m_view->mainWindow(), i18nc("@title:window", "Flatten Image"), i18n("The image contains hidden layers that will be lost. Do you want to flatten the image?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (answer != QMessageBox::Yes) { doIt = false; } } if (doIt) { image->flatten(); } } } inline bool isSelectionMask(KisNodeSP node) { return dynamic_cast(node.data()); } bool tryMergeSelectionMasks(KisNodeSP currentNode, KisImageSP image) { bool result = false; KisNodeSP prevNode = currentNode->prevSibling(); if (isSelectionMask(currentNode) && prevNode && isSelectionMask(prevNode)) { QList mergedNodes; mergedNodes.append(currentNode); mergedNodes.append(prevNode); image->mergeMultipleLayers(mergedNodes, currentNode); result = true; } return result; } void KisLayerManager::mergeLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; - if (!m_view->blockUntillOperationsFinished(image)) return; + if (!m_view->blockUntilOperationsFinished(image)) return; QList selectedNodes = m_view->nodeManager()->selectedNodes(); if (selectedNodes.size() > 1) { image->mergeMultipleLayers(selectedNodes, m_view->activeNode()); } else if (!tryMergeSelectionMasks(m_view->activeNode(), image)) { if (!layer->prevSibling()) return; KisLayer *prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (layer->metaData()->isEmpty() && prevLayer->metaData()->isEmpty()) { image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); } else { const KisMetaData::MergeStrategy* strategy = KisMetaDataMergeStrategyChooserWidget::showDialog(m_view->mainWindow()); if (!strategy) return; image->mergeDown(layer, strategy); } } m_view->updateGUI(); } void KisLayerManager::flattenLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; - if (!m_view->blockUntillOperationsFinished(image)) return; + if (!m_view->blockUntilOperationsFinished(image)) return; image->flattenLayer(layer); m_view->updateGUI(); } void KisLayerManager::rasterizeLayer() { KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; - if (!m_view->blockUntillOperationsFinished(image)) return; + if (!m_view->blockUntilOperationsFinished(image)) return; KisPaintLayerSP paintLayer = new KisPaintLayer(image, layer->name(), layer->opacity()); KisPainter gc(paintLayer->paintDevice()); QRect rc = layer->projection()->exactBounds(); gc.bitBlt(rc.topLeft(), layer->projection(), rc); m_commandsAdapter->beginMacro(kundo2_i18n("Rasterize Layer")); m_commandsAdapter->addNode(paintLayer.data(), layer->parent().data(), layer.data()); int childCount = layer->childCount(); for (int i = 0; i < childCount; i++) { m_commandsAdapter->moveNode(layer->firstChild(), paintLayer, paintLayer->lastChild()); } m_commandsAdapter->removeNode(layer); m_commandsAdapter->endMacro(); updateGUI(); } void KisLayerManager::layersUpdated() { KisLayerSP layer = activeLayer(); if (!layer) return; m_view->updateGUI(); } void KisLayerManager::saveGroupLayers() { QStringList listMimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export); KoDialog dlg; QWidget *page = new QWidget(&dlg); dlg.setMainWidget(page); QBoxLayout *layout = new QVBoxLayout(page); KisFileNameRequester *urlRequester = new KisFileNameRequester(page); urlRequester->setMode(KoFileDialog::SaveFile); if (m_view->document()->url().isLocalFile()) { urlRequester->setStartDir(QFileInfo(m_view->document()->url().toLocalFile()).absolutePath()); } urlRequester->setMimeTypeFilters(listMimeFilter); urlRequester->setFileName(m_view->document()->url().toLocalFile()); layout->addWidget(urlRequester); QCheckBox *chkInvisible = new QCheckBox(i18n("Convert Invisible Groups"), page); chkInvisible->setChecked(false); layout->addWidget(chkInvisible); QCheckBox *chkDepth = new QCheckBox(i18n("Export Only Toplevel Groups"), page); chkDepth->setChecked(true); layout->addWidget(chkDepth); if (!dlg.exec()) return; QString path = urlRequester->fileName(); if (path.isEmpty()) return; QFileInfo f(path); QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName()); if (mimeType.isEmpty()) { mimeType = "image/png"; } QString extension = KisMimeDatabase::suffixesForMimeType(mimeType).first(); QString basename = f.baseName(); KisImageSP image = m_view->image(); if (!image) return; KisSaveGroupVisitor v(image, chkInvisible->isChecked(), chkDepth->isChecked(), f.absolutePath(), basename, extension, mimeType); image->rootLayer()->accept(v); } bool KisLayerManager::activeLayerHasSelection() { return (activeLayer()->selection() != 0); } void KisLayerManager::addFileLayer(KisNodeSP activeNode) { QString basePath; QUrl url = m_view->document()->url(); if (url.isLocalFile()) { basePath = QFileInfo(url.toLocalFile()).absolutePath(); } KisImageWSP image = m_view->image(); KisDlgFileLayer dlg(basePath, image->nextLayerName(), m_view->mainWindow()); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { QString name = dlg.layerName(); QString fileName = dlg.fileName(); if(fileName.isEmpty()){ QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified")); return; } KisFileLayer::ScalingMethod scalingMethod = dlg.scaleToImageResolution(); addLayerCommon(activeNode, new KisFileLayer(image, basePath, fileName, scalingMethod, name, OPACITY_OPAQUE_U8)); } } void updateLayerStyles(KisLayerSP layer, KisDlgLayerStyle *dlg) { KisSetLayerStyleCommand::updateLayerStyle(layer, dlg->style()->clone()); } void KisLayerManager::layerStyle() { KisImageWSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; - if (!m_view->blockUntillOperationsFinished(image)) return; + if (!m_view->blockUntilOperationsFinished(image)) return; KisPSDLayerStyleSP oldStyle; if (layer->layerStyle()) { oldStyle = layer->layerStyle()->clone(); } else { oldStyle = toQShared(new KisPSDLayerStyle()); } KisDlgLayerStyle dlg(oldStyle->clone(), m_view->resourceProvider()); std::function updateCall(std::bind(updateLayerStyles, layer, &dlg)); SignalToFunctionProxy proxy(updateCall); connect(&dlg, SIGNAL(configChanged()), &proxy, SLOT(start())); if (dlg.exec() == QDialog::Accepted) { KisPSDLayerStyleSP newStyle = dlg.style(); KUndo2CommandSP command = toQShared( new KisSetLayerStyleCommand(layer, oldStyle, newStyle)); image->postExecutionUndoAdapter()->addCommand(command); } } diff --git a/libs/ui/kis_layer_manager.h b/libs/ui/kis_layer_manager.h index b61554aff9..824c3d14db 100644 --- a/libs/ui/kis_layer_manager.h +++ b/libs/ui/kis_layer_manager.h @@ -1,134 +1,131 @@ /* * Copyright (C) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_LAYER_MANAGER #define KIS_LAYER_MANAGER #include #include #include #include "kis_adjustment_layer.h" #include "kis_types.h" #include "KisView.h" #include class KisViewManager; class KisNodeCommandsAdapter; class KisAction; class KisActionManager; /** * KisLayerManager takes care of the gui around working with layers: * adding, removing, editing. It also keeps track of the active layer * for this view. */ class KisLayerManager : public QObject { Q_OBJECT public: KisLayerManager(KisViewManager * view); ~KisLayerManager(); void setView(QPointerview); Q_SIGNALS: void sigLayerActivated(KisLayerSP layer); private: friend class KisNodeManager; /** * Activate the specified layer. The layer may be 0. */ void activateLayer(KisLayerSP layer); KisLayerSP activeLayer(); KisPaintDeviceSP activeDevice(); void setup(KisActionManager *actionManager); void updateGUI(); - void rotateLayer(double radians); - void shearLayer(double angleX, double angleY); - private Q_SLOTS: void mergeLayer(); void imageResizeToActiveLayer(); void trimToImage(); void layerProperties(); void flattenImage(); void flattenLayer(); void rasterizeLayer(); void layersUpdated(); void saveGroupLayers(); bool activeLayerHasSelection(); void convertNodeToPaintLayer(KisNodeSP source); void convertGroupToAnimated(); KisLayerSP addLayer(KisNodeSP activeNode); void addGroupLayer(KisNodeSP activeNode); void addCloneLayer(KisNodeSP activeNode); void addShapeLayer(KisNodeSP activeNode); void addAdjustmentLayer(KisNodeSP activeNode); KisAdjustmentLayerSP addAdjustmentLayer(KisNodeSP activeNode, const QString & name, KisFilterConfigurationSP filter, KisSelectionSP selection); void addGeneratorLayer(KisNodeSP activeNode); void addFileLayer(KisNodeSP activeNode); void layerStyle(); private: void adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above); void addLayerCommon(KisNodeSP activeNode, KisLayerSP layer, bool updateImage = true); private: KisViewManager * m_view; QPointerm_imageView; KisAction *m_imageFlatten; KisAction *m_imageMergeLayer; KisAction *m_groupLayersSave; KisAction *m_convertGroupAnimated; KisAction *m_imageResizeToLayer; KisAction *m_flattenLayer; KisAction *m_rasterizeLayer; KisNodeCommandsAdapter* m_commandsAdapter; KisAction *m_layerStyle; }; #endif diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp index cd7f14fee8..a1699698df 100644 --- a/libs/ui/kis_node_manager.cpp +++ b/libs/ui/kis_node_manager.cpp @@ -1,1333 +1,1344 @@ /* * Copyright (C) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisPart.h" #include "canvas/kis_canvas2.h" #include "kis_shape_controller.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "KisDocument.h" #include "kis_mask_manager.h" #include "kis_group_layer.h" #include "kis_layer_manager.h" #include "kis_selection_manager.h" #include "kis_node_commands_adapter.h" #include "kis_action.h" #include "kis_action_manager.h" #include "kis_processing_applicator.h" #include "kis_sequential_iterator.h" #include "kis_transaction.h" #include "kis_node_selection_adapter.h" #include "kis_node_insertion_adapter.h" #include "kis_node_juggler_compressed.h" #include "kis_clipboard.h" #include "kis_node_dummies_graph.h" #include "kis_mimedata.h" #include "kis_layer_utils.h" #include "krita_utils.h" #include "processing/kis_mirror_processing_visitor.h" #include "KisView.h" struct KisNodeManager::Private { Private(KisNodeManager *_q, KisViewManager *v) : q(_q) , view(v) , imageView(0) , layerManager(v) , maskManager(v) , commandsAdapter(v) , nodeSelectionAdapter(new KisNodeSelectionAdapter(q)) , nodeInsertionAdapter(new KisNodeInsertionAdapter(q)) { } KisNodeManager * q; KisViewManager * view; QPointerimageView; KisLayerManager layerManager; KisMaskManager maskManager; KisNodeCommandsAdapter commandsAdapter; QScopedPointer nodeSelectionAdapter; QScopedPointer nodeInsertionAdapter; KisNodeList selectedNodes; QPointer nodeJuggler; KisNodeWSP previouslyActiveNode; bool activateNodeImpl(KisNodeSP node); QSignalMapper nodeCreationSignalMapper; QSignalMapper nodeConversionSignalMapper; void saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity); void mergeTransparencyMaskAsAlpha(bool writeToLayers); KisNodeJugglerCompressed* lazyGetJuggler(const KUndo2MagicString &actionName); }; bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node) { Q_ASSERT(view); Q_ASSERT(view->canvasBase()); Q_ASSERT(view->canvasBase()->globalShapeManager()); Q_ASSERT(imageView); if (node && node == q->activeNode()) { return false; } // Set the selection on the shape manager to the active layer // and set call KoSelection::setActiveLayer( KoShapeLayer* layer ) // with the parent of the active layer. KoSelection *selection = view->canvasBase()->globalShapeManager()->selection(); Q_ASSERT(selection); selection->deselectAll(); if (!node) { selection->setActiveLayer(0); imageView->setCurrentNode(0); maskManager.activateMask(0); layerManager.activateLayer(0); previouslyActiveNode = q->activeNode(); } else { previouslyActiveNode = q->activeNode(); KoShape * shape = view->document()->shapeForNode(node); KIS_ASSERT_RECOVER_RETURN_VALUE(shape, false); selection->select(shape); KoShapeLayer * shapeLayer = dynamic_cast(shape); KIS_ASSERT_RECOVER_RETURN_VALUE(shapeLayer, false); // shapeLayer->setGeometryProtected(node->userLocked()); // shapeLayer->setVisible(node->visible()); selection->setActiveLayer(shapeLayer); imageView->setCurrentNode(node); if (KisLayerSP layer = qobject_cast(node.data())) { maskManager.activateMask(0); layerManager.activateLayer(layer); } else if (KisMaskSP mask = dynamic_cast(node.data())) { maskManager.activateMask(mask); // XXX_NODE: for now, masks cannot be nested. layerManager.activateLayer(static_cast(node->parent().data())); } } return true; } KisNodeManager::KisNodeManager(KisViewManager *view) : m_d(new Private(this, view)) { connect(&m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP))); } KisNodeManager::~KisNodeManager() { delete m_d; } void KisNodeManager::setView(QPointerimageView) { m_d->maskManager.setView(imageView); m_d->layerManager.setView(imageView); if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); shapeController->disconnect(SIGNAL(sigActivateNode(KisNodeSP)), this); m_d->imageView->image()->disconnect(this); } m_d->imageView = imageView; if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); connect(shapeController, SIGNAL(sigActivateNode(KisNodeSP)), SLOT(slotNonUiActivatedNode(KisNodeSP))); connect(m_d->imageView->image(), SIGNAL(sigIsolatedModeChanged()),this, SLOT(slotUpdateIsolateModeAction())); connect(m_d->imageView->image(), SIGNAL(sigRequestNodeReselection(KisNodeSP, const KisNodeList&)),this, SLOT(slotImageRequestNodeReselection(KisNodeSP, const KisNodeList&))); m_d->imageView->resourceProvider()->slotNodeActivated(m_d->imageView->currentNode()); } } #define NEW_LAYER_ACTION(id, layerType) \ { \ action = actionManager->createAction(id); \ m_d->nodeCreationSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeCreationSignalMapper, SLOT(map())); \ } #define CONVERT_NODE_ACTION_2(id, layerType, exclude) \ { \ action = actionManager->createAction(id); \ action->setExcludedNodeTypes(QStringList(exclude)); \ actionManager->addAction(id, action); \ m_d->nodeConversionSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeConversionSignalMapper, SLOT(map())); \ } #define CONVERT_NODE_ACTION(id, layerType) \ CONVERT_NODE_ACTION_2(id, layerType, layerType) void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManager* actionManager) { m_d->layerManager.setup(actionManager); m_d->maskManager.setup(actionCollection, actionManager); KisAction * action = actionManager->createAction("mirrorNodeX"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeX())); action = actionManager->createAction("mirrorNodeY"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeY())); action = actionManager->createAction("activateNextLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode())); action = actionManager->createAction("activatePreviousLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode())); action = actionManager->createAction("switchToPreviouslyActiveNode"); connect(action, SIGNAL(triggered()), this, SLOT(switchToPreviouslyActiveNode())); action = actionManager->createAction("save_node_as_image"); connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage())); action = actionManager->createAction("duplicatelayer"); connect(action, SIGNAL(triggered()), this, SLOT(duplicateActiveNode())); action = actionManager->createAction("copy_layer_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(copyLayersToClipboard())); action = actionManager->createAction("cut_layer_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(cutLayersToClipboard())); action = actionManager->createAction("paste_layer_from_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(pasteLayersFromClipboard())); action = actionManager->createAction("create_quick_group"); connect(action, SIGNAL(triggered()), this, SLOT(createQuickGroup())); action = actionManager->createAction("create_quick_clipping_group"); connect(action, SIGNAL(triggered()), this, SLOT(createQuickClippingGroup())); action = actionManager->createAction("quick_ungroup"); connect(action, SIGNAL(triggered()), this, SLOT(quickUngroup())); action = actionManager->createAction("select_all_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectAllNodes())); action = actionManager->createAction("select_visible_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectVisibleNodes())); action = actionManager->createAction("select_locked_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectLockedNodes())); action = actionManager->createAction("select_invisible_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectInvisibleNodes())); action = actionManager->createAction("select_unlocked_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectUnlockedNodes())); action = actionManager->createAction("new_from_visible"); connect(action, SIGNAL(triggered()), this, SLOT(createFromVisible())); NEW_LAYER_ACTION("add_new_paint_layer", "KisPaintLayer"); NEW_LAYER_ACTION("add_new_group_layer", "KisGroupLayer"); NEW_LAYER_ACTION("add_new_clone_layer", "KisCloneLayer"); NEW_LAYER_ACTION("add_new_shape_layer", "KisShapeLayer"); NEW_LAYER_ACTION("add_new_adjustment_layer", "KisAdjustmentLayer"); NEW_LAYER_ACTION("add_new_fill_layer", "KisGeneratorLayer"); NEW_LAYER_ACTION("add_new_file_layer", "KisFileLayer"); NEW_LAYER_ACTION("add_new_transparency_mask", "KisTransparencyMask"); NEW_LAYER_ACTION("add_new_filter_mask", "KisFilterMask"); NEW_LAYER_ACTION("add_new_colorize_mask", "KisColorizeMask"); NEW_LAYER_ACTION("add_new_transform_mask", "KisTransformMask"); NEW_LAYER_ACTION("add_new_selection_mask", "KisSelectionMask"); connect(&m_d->nodeCreationSignalMapper, SIGNAL(mapped(const QString &)), this, SLOT(createNode(const QString &))); CONVERT_NODE_ACTION("convert_to_paint_layer", "KisPaintLayer"); CONVERT_NODE_ACTION_2("convert_to_selection_mask", "KisSelectionMask", QStringList() << "KisSelectionMask" << "KisColorizeMask"); CONVERT_NODE_ACTION_2("convert_to_filter_mask", "KisFilterMask", QStringList() << "KisFilterMask" << "KisColorizeMask"); CONVERT_NODE_ACTION_2("convert_to_transparency_mask", "KisTransparencyMask", QStringList() << "KisTransparencyMask" << "KisColorizeMask"); CONVERT_NODE_ACTION("convert_to_animated", "animated"); connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(const QString &)), this, SLOT(convertNode(const QString &))); action = actionManager->createAction("isolate_layer"); connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool))); action = actionManager->createAction("split_alpha_into_mask"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask())); action = actionManager->createAction("split_alpha_write"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite())); // HINT: we can save even when the nodes are not editable action = actionManager->createAction("split_alpha_save_merged"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotTryFinishIsolatedMode())); } void KisNodeManager::updateGUI() { // enable/disable all relevant actions m_d->layerManager.updateGUI(); m_d->maskManager.updateGUI(); } KisNodeSP KisNodeManager::activeNode() { if (m_d->imageView) { return m_d->imageView->currentNode(); } return 0; } KisLayerSP KisNodeManager::activeLayer() { return m_d->layerManager.activeLayer(); } const KoColorSpace* KisNodeManager::activeColorSpace() { if (m_d->maskManager.activeDevice()) { return m_d->maskManager.activeDevice()->colorSpace(); } else { Q_ASSERT(m_d->layerManager.activeLayer()); if (m_d->layerManager.activeLayer()->parentLayer()) return m_d->layerManager.activeLayer()->parentLayer()->colorSpace(); else return m_d->view->image()->colorSpace(); } } void KisNodeManager::moveNodeAt(KisNodeSP node, KisNodeSP parent, int index) { if (parent->allowAsChild(node)) { if (node->inherits("KisSelectionMask") && parent->inherits("KisLayer")) { KisSelectionMask *m = dynamic_cast(node.data()); KisLayer *l = qobject_cast(parent.data()); KisSelectionMaskSP selMask = l->selectionMask(); if (m && m->active() && l && l->selectionMask()) selMask->setActive(false); } m_d->commandsAdapter.moveNode(node, parent, index); } } void KisNodeManager::moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Move Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, aboveThis); } void KisNodeManager::copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Copy Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->copyNode(nodes, parent, aboveThis); } void KisNodeManager::addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Add Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->addNode(nodes, parent, aboveThis); } void KisNodeManager::toggleIsolateActiveNode() { KisImageWSP image = m_d->view->image(); KisNodeSP activeNode = this->activeNode(); KIS_ASSERT_RECOVER_RETURN(activeNode); if (activeNode == image->isolatedModeRoot()) { toggleIsolateMode(false); } else { toggleIsolateMode(true); } } void KisNodeManager::toggleIsolateMode(bool checked) { KisImageWSP image = m_d->view->image(); if (checked) { KisNodeSP activeNode = this->activeNode(); // Transform and colorize masks don't have pixel data... if (activeNode->inherits("KisTransformMask") || activeNode->inherits("KisColorizeMask")) return; KIS_ASSERT_RECOVER_RETURN(activeNode); if (!image->startIsolatedMode(activeNode)) { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); action->setChecked(false); } } else { image->stopIsolatedMode(); } } void KisNodeManager::slotUpdateIsolateModeAction() { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); Q_ASSERT(action); KisNodeSP activeNode = this->activeNode(); KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); action->setChecked(isolatedRootNode && isolatedRootNode == activeNode); } void KisNodeManager::slotTryFinishIsolatedMode() { KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); if (!isolatedRootNode) return; this->toggleIsolateMode(true); } void KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom) { KisNodeSP activeNode = this->activeNode(); if (!activeNode) { activeNode = m_d->view->image()->root(); } KIS_ASSERT_RECOVER_RETURN(activeNode); if (activeNode->systemLocked()) { return; } // XXX: make factories for this kind of stuff, // with a registry if (nodeType == "KisPaintLayer") { m_d->layerManager.addLayer(activeNode); } else if (nodeType == "KisGroupLayer") { m_d->layerManager.addGroupLayer(activeNode); } else if (nodeType == "KisAdjustmentLayer") { m_d->layerManager.addAdjustmentLayer(activeNode); } else if (nodeType == "KisGeneratorLayer") { m_d->layerManager.addGeneratorLayer(activeNode); } else if (nodeType == "KisShapeLayer") { m_d->layerManager.addShapeLayer(activeNode); } else if (nodeType == "KisCloneLayer") { m_d->layerManager.addCloneLayer(activeNode); } else if (nodeType == "KisTransparencyMask") { m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false); } else if (nodeType == "KisFilterMask") { m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false); } else if (nodeType == "KisColorizeMask") { m_d->maskManager.createColorizeMask(activeNode); } else if (nodeType == "KisTransformMask") { m_d->maskManager.createTransformMask(activeNode); } else if (nodeType == "KisSelectionMask") { m_d->maskManager.createSelectionMask(activeNode, copyFrom, false); } else if (nodeType == "KisFileLayer") { m_d->layerManager.addFileLayer(activeNode); } } void KisNodeManager::createFromVisible() { KisLayerUtils::newLayerFromVisible(m_d->view->image(), m_d->view->image()->root()->lastChild()); } KisLayerSP KisNodeManager::createPaintLayer() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) { activeNode = m_d->view->image()->root(); } return m_d->layerManager.addLayer(activeNode); } void KisNodeManager::convertNode(const QString &nodeType) { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; if (nodeType == "KisPaintLayer") { m_d->layerManager.convertNodeToPaintLayer(activeNode); } else if (nodeType == "KisSelectionMask" || nodeType == "KisFilterMask" || nodeType == "KisTransparencyMask") { KisPaintDeviceSP copyFrom = activeNode->paintDevice() ? activeNode->paintDevice() : activeNode->projection(); m_d->commandsAdapter.beginMacro(kundo2_i18n("Convert to a Selection Mask")); if (nodeType == "KisSelectionMask") { m_d->maskManager.createSelectionMask(activeNode, copyFrom, true); } else if (nodeType == "KisFilterMask") { m_d->maskManager.createFilterMask(activeNode, copyFrom, false, true); } else if (nodeType == "KisTransparencyMask") { m_d->maskManager.createTransparencyMask(activeNode, copyFrom, true); } m_d->commandsAdapter.removeNode(activeNode); m_d->commandsAdapter.endMacro(); } else { warnKrita << "Unsupported node conversion type:" << nodeType; } } void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node) { KIS_ASSERT_RECOVER_RETURN(node != activeNode()); if (m_d->activateNodeImpl(node)) { emit sigUiNeedChangeActiveNode(node); emit sigNodeActivated(node); nodesUpdated(); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } } void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node) { if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } void KisNodeManager::slotUiActivatedNode(KisNodeSP node) { if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); if (node) { QStringList vectorTools = QStringList() << "InteractionTool" << "KarbonPatternTool" << "KarbonGradientTool" << "KarbonCalligraphyTool" << "CreateShapesTool" << "PathTool"; QStringList pixelTools = QStringList() << "KritaShape/KisToolBrush" << "KritaShape/KisToolDyna" << "KritaShape/KisToolMultiBrush" << "KritaFill/KisToolFill" << "KritaFill/KisToolGradient"; if (node->inherits("KisShapeLayer")) { if (pixelTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("InteractionTool"); } } else { if (vectorTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } } } } void KisNodeManager::nodesUpdated() { KisNodeSP node = activeNode(); if (!node) return; m_d->layerManager.layersUpdated(); m_d->maskManager.masksUpdated(); m_d->view->updateGUI(); m_d->view->selectionManager()->selectionChanged(); } KisPaintDeviceSP KisNodeManager::activePaintDevice() { return m_d->maskManager.activeMask() ? m_d->maskManager.activeDevice() : m_d->layerManager.activeDevice(); } void KisNodeManager::nodeProperties(KisNodeSP node) { if (selectedNodes().size() > 1 || node->inherits("KisLayer")) { m_d->layerManager.layerProperties(); } else if (node->inherits("KisMask")) { m_d->maskManager.maskProperties(); } } qint32 KisNodeManager::convertOpacityToInt(qreal opacity) { /** * Scales opacity from the range 0...100 * to the integer range 0...255 */ return qMin(255, int(opacity * 2.55 + 0.5)); } void KisNodeManager::setNodeOpacity(KisNodeSP node, qint32 opacity, bool finalChange) { if (!node) return; if (node->opacity() == opacity) return; if (!finalChange) { node->setOpacity(opacity); node->setDirty(); } else { m_d->commandsAdapter.setOpacity(node, opacity); } } void KisNodeManager::setNodeCompositeOp(KisNodeSP node, const KoCompositeOp* compositeOp) { if (!node) return; if (node->compositeOp() == compositeOp) return; m_d->commandsAdapter.setCompositeOp(node, compositeOp); } void KisNodeManager::slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes) { if (activeNode) { slotNonUiActivatedNode(activeNode); } if (!selectedNodes.isEmpty()) { slotSetSelectedNodes(selectedNodes); } } void KisNodeManager::slotSetSelectedNodes(const KisNodeList &nodes) { m_d->selectedNodes = nodes; emit sigUiNeedChangeSelectedNodes(nodes); } KisNodeList KisNodeManager::selectedNodes() { return m_d->selectedNodes; } KisNodeSelectionAdapter* KisNodeManager::nodeSelectionAdapter() const { return m_d->nodeSelectionAdapter.data(); } KisNodeInsertionAdapter* KisNodeManager::nodeInsertionAdapter() const { return m_d->nodeInsertionAdapter.data(); } void KisNodeManager::nodeOpacityChanged(qreal opacity, bool finalChange) { KisNodeSP node = activeNode(); setNodeOpacity(node, convertOpacityToInt(opacity), finalChange); } void KisNodeManager::nodeCompositeOpChanged(const KoCompositeOp* op) { KisNodeSP node = activeNode(); setNodeCompositeOp(node, op); } void KisNodeManager::duplicateActiveNode() { KUndo2MagicString actionName = kundo2_i18n("Duplicate Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->duplicateNode(selectedNodes()); } KisNodeJugglerCompressed* KisNodeManager::Private::lazyGetJuggler(const KUndo2MagicString &actionName) { KisImageWSP image = view->image(); if (!nodeJuggler || (nodeJuggler && !nodeJuggler->canMergeAction(actionName))) { nodeJuggler = new KisNodeJugglerCompressed(actionName, image, q, 750); nodeJuggler->setAutoDelete(true); } return nodeJuggler; } void KisNodeManager::raiseNode() { KUndo2MagicString actionName = kundo2_i18n("Raise Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->raiseNode(selectedNodes()); } void KisNodeManager::lowerNode() { KUndo2MagicString actionName = kundo2_i18n("Lower Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->lowerNode(selectedNodes()); } void KisNodeManager::removeSingleNode(KisNodeSP node) { if (!node || !node->parent()) { return; } KisNodeList nodes; nodes << node; removeSelectedNodes(nodes); } void KisNodeManager::removeSelectedNodes(KisNodeList nodes) { KUndo2MagicString actionName = kundo2_i18n("Remove Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::removeNode() { removeSelectedNodes(selectedNodes()); } void KisNodeManager::mirrorNodeX() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer X"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask X"); } mirrorNode(node, commandName, Qt::Horizontal); } void KisNodeManager::mirrorNodeY() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer Y"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask Y"); } mirrorNode(node, commandName, Qt::Vertical); } inline bool checkForGlobalSelection(KisNodeSP node) { return dynamic_cast(node.data()) && node->parent() && !node->parent()->parent(); } void KisNodeManager::activateNextNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node = activeNode->nextSibling(); while (node && node->childCount() > 0) { node = node->firstChild(); } if (!node && activeNode->parent() && activeNode->parent()->parent()) { node = activeNode->parent(); } while(node && checkForGlobalSelection(node)) { node = node->nextSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::activatePreviousNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node; if (activeNode->childCount() > 0) { node = activeNode->lastChild(); } else { node = activeNode->prevSibling(); } while (!node && activeNode->parent()) { node = activeNode->parent()->prevSibling(); activeNode = activeNode->parent(); } while(node && checkForGlobalSelection(node)) { node = node->prevSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::switchToPreviouslyActiveNode() { if (m_d->previouslyActiveNode && m_d->previouslyActiveNode->parent()) { slotNonUiActivatedNode(m_d->previouslyActiveNode); } } void KisNodeManager::mergeLayer() { m_d->layerManager.mergeLayer(); } void KisNodeManager::rotate(double radians) { - // XXX: implement rotation for masks as well - m_d->layerManager.rotateLayer(radians); + if(!m_d->view->image()) return; + KisNodeSP node = activeNode(); + if (!node) return; + + if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) return; + + m_d->view->image()->rotateNode(node, radians); } void KisNodeManager::rotate180() { rotate(M_PI); } void KisNodeManager::rotateLeft90() { rotate(-M_PI / 2); } void KisNodeManager::rotateRight90() { rotate(M_PI / 2); } void KisNodeManager::shear(double angleX, double angleY) { - // XXX: implement shear for masks as well - m_d->layerManager.shearLayer(angleX, angleY); + if (!m_d->view->image()) return; + + KisNodeSP node = activeNode(); + if (!node) return; + + if(!m_d->view->blockUntilOperationsFinished(m_d->view->image())) return; + + m_d->view->image()->shearNode(node, angleX, angleY); } void KisNodeManager::scale(double sx, double sy, KisFilterStrategy *filterStrategy) { KisNodeSP node = activeNode(); KIS_ASSERT_RECOVER_RETURN(node); m_d->view->image()->scaleNode(node, sx, sy, filterStrategy); nodesUpdated(); } void KisNodeManager::mirrorNode(KisNodeSP node, const KUndo2MagicString& actionName, Qt::Orientation orientation) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(m_d->view->image(), node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor = new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); nodesUpdated(); } void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity) { KoFileDialog dialog(view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage"); dialog.setCaption(i18n("Export \"%1\"", defaultName)); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Export)); QString filename = dialog.filename(); if (filename.isEmpty()) return; QUrl url = QUrl::fromLocalFile(filename); if (url.isEmpty()) return; QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename);; QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(doc->createUndoStore(), bounds.width(), bounds.height(), device->compositionSourceColorSpace(), defaultName); dst->setResolution(xRes, yRes); doc->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity); paintLayer->paintDevice()->makeCloneFrom(device, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->initialRefreshGraph(); doc->setOutputMimeType(mimefilter.toLatin1()); doc->exportDocument(url); } void KisNodeManager::saveNodeAsImage() { KisNodeSP node = activeNode(); if (!node) { warnKrita << "BUG: Save Node As Image was called without any node selected"; return; } KisImageWSP image = m_d->view->image(); QRect saveRect = image->bounds() | node->exactBounds(); KisPaintDeviceSP device = node->paintDevice(); if (!device) { device = node->projection(); } m_d->saveDeviceAsImage(device, node->name(), saveRect, image->xRes(), image->yRes(), node->opacity()); } void KisNodeManager::slotSplitAlphaIntoMask() { KisNodeSP node = activeNode(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice()); KisPaintDeviceSP srcDevice = node->paintDevice(); const KoColorSpace *srcCS = srcDevice->colorSpace(); const QRect processRect = srcDevice->exactBounds() | srcDevice->defaultBounds()->bounds(); KisPaintDeviceSP selectionDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); m_d->commandsAdapter.beginMacro(kundo2_i18n("Split Alpha into a Mask")); KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice); KisSequentialIterator srcIt(srcDevice, processRect); KisSequentialIterator dstIt(selectionDevice, processRect); do { quint8 *srcPtr = srcIt.rawData(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->opacityU8(srcPtr); srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1); } while (srcIt.nextPixel() && dstIt.nextPixel()); m_d->commandsAdapter.addExtraCommand(transaction.endAndTake()); createNode("KisTransparencyMask", false, selectionDevice); m_d->commandsAdapter.endMacro(); } void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers) { KisNodeSP node = q->activeNode(); KisNodeSP parentNode = node->parent(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask")); if (writeToLayers && !parentNode->hasEditablePaintDevice()) { QMessageBox::information(view->mainWindow(), i18nc("@title:window", "Layer %1 is not editable").arg(parentNode->name()), i18n("Cannot write alpha channel of " "the parent layer \"%1\".\n" "The operation will be cancelled.").arg(parentNode->name())); return; } KisPaintDeviceSP dstDevice; if (writeToLayers) { KIS_ASSERT_RECOVER_RETURN(parentNode->paintDevice()); dstDevice = parentNode->paintDevice(); } else { KisPaintDeviceSP copyDevice = parentNode->paintDevice(); if (!copyDevice) { copyDevice = parentNode->original(); } dstDevice = new KisPaintDevice(*copyDevice); } const KoColorSpace *dstCS = dstDevice->colorSpace(); KisPaintDeviceSP selectionDevice = node->paintDevice(); KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1); const QRect processRect = selectionDevice->exactBounds() | dstDevice->exactBounds() | selectionDevice->defaultBounds()->bounds(); QScopedPointer transaction; if (writeToLayers) { commandsAdapter.beginMacro(kundo2_i18n("Write Alpha into a Layer")); transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice)); } KisSequentialIterator srcIt(selectionDevice, processRect); KisSequentialIterator dstIt(dstDevice, processRect); do { quint8 *alpha8Ptr = srcIt.rawData(); quint8 *dstPtr = dstIt.rawData(); dstCS->setOpacity(dstPtr, *alpha8Ptr, 1); } while (srcIt.nextPixel() && dstIt.nextPixel()); if (writeToLayers) { commandsAdapter.addExtraCommand(transaction->endAndTake()); commandsAdapter.removeNode(node); commandsAdapter.endMacro(); } else { KisImageWSP image = view->image(); QRect saveRect = image->bounds(); saveDeviceAsImage(dstDevice, parentNode->name(), saveRect, image->xRes(), image->yRes(), OPACITY_OPAQUE_U8); } } void KisNodeManager::slotSplitAlphaWrite() { m_d->mergeTransparencyMaskAsAlpha(true); } void KisNodeManager::slotSplitAlphaSaveMerged() { m_d->mergeTransparencyMaskAsAlpha(false); } void KisNodeManager::cutLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); KisNodeSP root = m_d->view->image()->root(); if (nodes.isEmpty()) return; KisClipboard::instance()->setLayers(nodes, root, false); KUndo2MagicString actionName = kundo2_i18n("Cut Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::copyLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); KisNodeSP root = m_d->view->image()->root(); KisClipboard::instance()->setLayers(nodes, root, true); } void KisNodeManager::pasteLayersFromClipboard() { const QMimeData *data = KisClipboard::instance()->layersMimeData(); if (!data) return; KisNodeSP activeNode = this->activeNode(); KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(dummiesFacade); const bool copyNode = false; KisImageSP image = m_d->view->image(); KisNodeDummy *parentDummy = dummiesFacade->dummyForNode(activeNode); KisNodeDummy *aboveThisDummy = parentDummy ? parentDummy->lastChild() : 0; KisMimeData::insertMimeLayers(data, image, shapeController, parentDummy, aboveThisDummy, copyNode, nodeInsertionAdapter()); } void KisNodeManager::createQuickGroupImpl(KisNodeJugglerCompressed *juggler, const QString &overrideGroupName, KisNodeSP *newGroup, KisNodeSP *newLastChild) { KisNodeSP active = activeNode(); if (!active) return; KisImageSP image = m_d->view->image(); QString groupName = !overrideGroupName.isEmpty() ? overrideGroupName : image->nextLayerName(); KisGroupLayerSP group = new KisGroupLayer(image.data(), groupName, OPACITY_OPAQUE_U8); KisNodeList nodes1; nodes1 << group; KisNodeList nodes2; nodes2 = KisLayerUtils::sortMergableNodes(image->root(), selectedNodes()); KisLayerUtils::filterMergableNodes(nodes2); if (KisLayerUtils::checkIsChildOf(active, nodes2)) { active = nodes2.first(); } KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; juggler->addNode(nodes1, parent, aboveThis); juggler->moveNode(nodes2, group, 0); *newGroup = group; *newLastChild = nodes2.last(); } void KisNodeManager::createQuickGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; createQuickGroupImpl(juggler, "", &parent, &above); } void KisNodeManager::createQuickClippingGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Clipping Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; KisImageSP image = m_d->view->image(); createQuickGroupImpl(juggler, image->nextLayerName(i18nc("default name for a clipping group layer", "Clipping Group")), &parent, &above); KisPaintLayerSP maskLayer = new KisPaintLayer(image.data(), i18nc("default name for quick clip group mask layer", "Mask Layer"), OPACITY_OPAQUE_U8, image->colorSpace()); maskLayer->disableAlphaChannel(true); juggler->addNode(KisNodeList() << maskLayer, parent, above); } void KisNodeManager::quickUngroup() { KisNodeSP active = activeNode(); if (!active) return; KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; KUndo2MagicString actionName = kundo2_i18n("Quick Ungroup"); if (parent && dynamic_cast(active.data())) { KisNodeList nodes = active->childNodes(QStringList(), KoProperties()); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, active); juggler->removeNode(KisNodeList() << active); } else if (parent && parent->parent()) { KisNodeSP grandParent = parent->parent(); KisNodeList allChildNodes = parent->childNodes(QStringList(), KoProperties()); KisNodeList allSelectedNodes = selectedNodes(); const bool removeParent = KritaUtils::compareListsUnordered(allChildNodes, allSelectedNodes); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(allSelectedNodes, grandParent, parent); if (removeParent) { juggler->removeNode(KisNodeList() << parent); } } } void KisNodeManager::selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps) { KisImageSP image = m_d->view->image(); KisNodeList nodes = KisLayerUtils::findNodesWithProps(image->root(), props, true); KisNodeList selectedNodes = this->selectedNodes(); if (KritaUtils::compareListsUnordered(nodes, selectedNodes)) { nodes = KisLayerUtils::findNodesWithProps(image->root(), invertedProps, true); } if (!nodes.isEmpty()) { slotImageRequestNodeReselection(nodes.last(), nodes); } } void KisNodeManager::selectAllNodes() { KoProperties props; selectLayersImpl(props, props); } void KisNodeManager::selectVisibleNodes() { KoProperties props; props.setProperty("visible", true); KoProperties invertedProps; invertedProps.setProperty("visible", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectLockedNodes() { KoProperties props; props.setProperty("locked", true); KoProperties invertedProps; invertedProps.setProperty("locked", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectInvisibleNodes() { KoProperties props; props.setProperty("visible", false); KoProperties invertedProps; invertedProps.setProperty("visible", true); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectUnlockedNodes() { KoProperties props; props.setProperty("locked", false); KoProperties invertedProps; invertedProps.setProperty("locked", true); selectLayersImpl(props, invertedProps); } diff --git a/libs/ui/kis_selection_manager.cc b/libs/ui/kis_selection_manager.cc index dcd88395fa..97068393ef 100644 --- a/libs/ui/kis_selection_manager.cc +++ b/libs/ui/kis_selection_manager.cc @@ -1,620 +1,624 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * * The outline algorith uses the limn algorithm of fontutils by * Karl Berry and Kathryn Hargreaves * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection_manager.h" #include #include #include #include #include #include #include #include #include #include #include "KoCanvasController.h" #include "KoChannelInfo.h" #include "KoIntegerMaths.h" #include #include #include #include #include #include #include #include #include #include #include "kis_adjustment_layer.h" #include "kis_node_manager.h" #include "canvas/kis_canvas2.h" #include "kis_config.h" #include "kis_convolution_painter.h" #include "kis_convolution_kernel.h" #include "kis_debug.h" #include "KisDocument.h" #include "kis_fill_painter.h" #include "kis_group_layer.h" #include "kis_layer.h" #include "kis_statusbar.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_transaction.h" #include "kis_selection.h" #include "kis_types.h" #include "kis_canvas_resource_provider.h" #include "kis_undo_adapter.h" #include "kis_pixel_selection.h" #include "flake/kis_shape_selection.h" #include "commands/kis_selection_commands.h" #include "kis_selection_mask.h" #include "flake/kis_shape_layer.h" #include "kis_selection_decoration.h" #include "canvas/kis_canvas_decoration.h" #include "kis_node_commands_adapter.h" #include "kis_iterator_ng.h" #include "kis_clipboard.h" #include "KisViewManager.h" #include "kis_selection_filters.h" #include "kis_figure_painting_tool_helper.h" #include "KisView.h" #include "dialogs/kis_dlg_stroke_selection_properties.h" #include "actions/kis_selection_action_factories.h" #include "kis_action.h" #include "kis_action_manager.h" #include "operations/kis_operation_configuration.h" //new #include "kis_recorded_path_paint_action.h" #include "kis_node_query_path.h" #include "kis_tool_shape.h" KisSelectionManager::KisSelectionManager(KisViewManager * view) : m_view(view), m_doc(0), m_imageView(0), m_adapter(new KisNodeCommandsAdapter(view)), m_copy(0), m_copyMerged(0), m_cut(0), m_paste(0), m_pasteNew(0), m_cutToNewLayer(0), m_selectAll(0), m_deselect(0), m_clear(0), m_reselect(0), m_invert(0), m_copyToNewLayer(0), m_fillForegroundColor(0), m_fillBackgroundColor(0), m_fillPattern(0), m_imageResizeToSelection(0), m_selectionDecoration(0) { m_clipboard = KisClipboard::instance(); } KisSelectionManager::~KisSelectionManager() { } void KisSelectionManager::setup(KisActionManager* actionManager) { m_cut = actionManager->createStandardAction(KStandardAction::Cut, this, SLOT(cut())); m_copy = actionManager->createStandardAction(KStandardAction::Copy, this, SLOT(copy())); m_paste = actionManager->createStandardAction(KStandardAction::Paste, this, SLOT(paste())); KisAction *action = actionManager->createAction("copy_sharp"); connect(action, SIGNAL(triggered()), this, SLOT(copySharp())); action = actionManager->createAction("cut_sharp"); connect(action, SIGNAL(triggered()), this, SLOT(cutSharp())); m_pasteNew = actionManager->createAction("paste_new"); connect(m_pasteNew, SIGNAL(triggered()), this, SLOT(pasteNew())); m_pasteAt = actionManager->createAction("paste_at"); connect(m_pasteAt, SIGNAL(triggered()), this, SLOT(pasteAt())); m_copyMerged = actionManager->createAction("copy_merged"); connect(m_copyMerged, SIGNAL(triggered()), this, SLOT(copyMerged())); m_selectAll = actionManager->createAction("select_all"); connect(m_selectAll, SIGNAL(triggered()), this, SLOT(selectAll())); m_deselect = actionManager->createAction("deselect"); connect(m_deselect, SIGNAL(triggered()), this, SLOT(deselect())); m_clear = actionManager->createAction("clear"); connect(m_clear, SIGNAL(triggered()), SLOT(clear())); m_reselect = actionManager->createAction("reselect"); connect(m_reselect, SIGNAL(triggered()), this, SLOT(reselect())); m_invert = actionManager->createAction("invert"); m_invert->setOperationID("invertselection"); actionManager->registerOperation(new KisInvertSelectionOperation); m_copyToNewLayer = actionManager->createAction("copy_selection_to_new_layer"); connect(m_copyToNewLayer, SIGNAL(triggered()), this, SLOT(copySelectionToNewLayer())); m_cutToNewLayer = actionManager->createAction("cut_selection_to_new_layer"); connect(m_cutToNewLayer, SIGNAL(triggered()), this, SLOT(cutToNewLayer())); m_fillForegroundColor = actionManager->createAction("fill_selection_foreground_color"); connect(m_fillForegroundColor, SIGNAL(triggered()), this, SLOT(fillForegroundColor())); m_fillBackgroundColor = actionManager->createAction("fill_selection_background_color"); connect(m_fillBackgroundColor, SIGNAL(triggered()), this, SLOT(fillBackgroundColor())); m_fillPattern = actionManager->createAction("fill_selection_pattern"); connect(m_fillPattern, SIGNAL(triggered()), this, SLOT(fillPattern())); m_fillForegroundColorOpacity = actionManager->createAction("fill_selection_foreground_color_opacity"); connect(m_fillForegroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillForegroundColorOpacity())); m_fillBackgroundColorOpacity = actionManager->createAction("fill_selection_background_color_opacity"); connect(m_fillBackgroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillBackgroundColorOpacity())); m_fillPatternOpacity = actionManager->createAction("fill_selection_pattern_opacity"); connect(m_fillPatternOpacity, SIGNAL(triggered()), this, SLOT(fillPatternOpacity())); m_strokeShapes = actionManager->createAction("stroke_shapes"); connect(m_strokeShapes, SIGNAL(triggered()), this, SLOT(paintSelectedShapes())); m_toggleDisplaySelection = actionManager->createAction("toggle_display_selection"); connect(m_toggleDisplaySelection, SIGNAL(triggered()), this, SLOT(toggleDisplaySelection())); m_toggleDisplaySelection->setChecked(true); m_imageResizeToSelection = actionManager->createAction("resizeimagetoselection"); connect(m_imageResizeToSelection, SIGNAL(triggered()), this, SLOT(imageResizeToSelection())); action = actionManager->createAction("convert_to_vector_selection"); connect(action, SIGNAL(triggered()), SLOT(convertToVectorSelection())); action = actionManager->createAction("convert_shapes_to_vector_selection"); connect(action, SIGNAL(triggered()), SLOT(convertShapesToVectorSelection())); action = actionManager->createAction("convert_selection_to_shape"); connect(action, SIGNAL(triggered()), SLOT(convertToShape())); m_toggleSelectionOverlayMode = actionManager->createAction("toggle-selection-overlay-mode"); connect(m_toggleSelectionOverlayMode, SIGNAL(triggered()), SLOT(slotToggleSelectionDecoration())); m_strokeSelected = actionManager->createAction("stroke_selection"); connect(m_strokeSelected, SIGNAL(triggered()), SLOT(slotStrokeSelection())); QClipboard *cb = QApplication::clipboard(); connect(cb, SIGNAL(dataChanged()), SLOT(clipboardDataChanged())); } void KisSelectionManager::setView(QPointerimageView) { if (m_imageView && m_imageView->canvasBase()) { disconnect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(const QString&)), this, SLOT(clipboardDataChanged())); KoSelection *selection = m_imageView->canvasBase()->globalShapeManager()->selection(); selection->disconnect(this, SLOT(shapeSelectionChanged())); KisSelectionDecoration *decoration = qobject_cast(m_imageView->canvasBase()->decoration("selection").data()); if (decoration) { disconnect(SIGNAL(currentSelectionChanged()), decoration); } m_imageView->image()->undoAdapter()->disconnect(this); m_selectionDecoration = 0; } m_imageView = imageView; if (m_imageView) { KoSelection * selection = m_imageView->canvasBase()->globalShapeManager()->selection(); Q_ASSERT(selection); connect(selection, SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged())); KisSelectionDecoration* decoration = qobject_cast(m_imageView->canvasBase()->decoration("selection").data()); if (!decoration) { decoration = new KisSelectionDecoration(m_imageView); decoration->setVisible(true); m_imageView->canvasBase()->addDecoration(decoration); } m_selectionDecoration = decoration; connect(this, SIGNAL(currentSelectionChanged()), decoration, SLOT(selectionChanged())); connect(m_imageView->image()->undoAdapter(), SIGNAL(selectionChanged()), SLOT(selectionChanged())); connect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(const QString&)), SLOT(clipboardDataChanged())); } } void KisSelectionManager::clipboardDataChanged() { m_view->updateGUI(); } bool KisSelectionManager::havePixelsSelected() { KisSelectionSP activeSelection = m_view->selection(); return activeSelection && !activeSelection->selectedRect().isEmpty(); } bool KisSelectionManager::havePixelsInClipboard() { return m_clipboard->hasClip(); } bool KisSelectionManager::haveShapesSelected() { - if (m_view && m_view->canvasBase() && m_view->canvasBase()->shapeManager() && m_view->canvasBase()->shapeManager()->selection()->count()) { + if (m_view + && m_view->canvasBase() + && m_view->canvasBase()->shapeManager() + && m_view->canvasBase()->shapeManager()->selection() + && m_view->canvasBase()->shapeManager()->selection()->count()) { return m_view->canvasBase()->shapeManager()->selection()->count() > 0; } return false; } bool KisSelectionManager::haveShapesInClipboard() { KisShapeLayer *shapeLayer = dynamic_cast(m_view->activeLayer().data()); if (shapeLayer) { const QMimeData* data = QApplication::clipboard()->mimeData(); if (data) { QStringList mimeTypes = m_view->canvasBase()->toolProxy()->supportedPasteMimeTypes(); Q_FOREACH (const QString & mimeType, mimeTypes) { if (data->hasFormat(mimeType)) { return true; } } } } return false; } bool KisSelectionManager::havePixelSelectionWithPixels() { KisSelectionSP selection = m_view->selection(); if (selection && selection->hasPixelSelection()) { return !selection->pixelSelection()->selectedRect().isEmpty(); } return false; } void KisSelectionManager::updateGUI() { Q_ASSERT(m_view); Q_ASSERT(m_clipboard); if (!m_view || !m_clipboard) return; bool havePixelsSelected = this->havePixelsSelected(); bool havePixelsInClipboard = this->havePixelsInClipboard(); bool haveShapesSelected = this->haveShapesSelected(); bool haveShapesInClipboard = this->haveShapesInClipboard(); bool haveDevice = m_view->activeDevice(); KisLayerSP activeLayer = m_view->activeLayer(); KisImageWSP image = activeLayer ? activeLayer->image() : 0; bool canReselect = image && image->canReselectGlobalSelection(); bool canDeselect = image && image->globalSelection(); m_clear->setEnabled(haveDevice || havePixelsSelected || haveShapesSelected); m_cut->setEnabled(havePixelsSelected || haveShapesSelected); m_copy->setEnabled(havePixelsSelected || haveShapesSelected); m_paste->setEnabled(havePixelsInClipboard || haveShapesInClipboard); m_pasteAt->setEnabled(havePixelsInClipboard || haveShapesInClipboard); // FIXME: how about pasting shapes? m_pasteNew->setEnabled(havePixelsInClipboard); m_selectAll->setEnabled(true); m_deselect->setEnabled(canDeselect); m_reselect->setEnabled(canReselect); // m_load->setEnabled(true); // m_save->setEnabled(havePixelsSelected); updateStatusBar(); emit signalUpdateGUI(); } void KisSelectionManager::updateStatusBar() { if (m_view && m_view->statusBar()) { m_view->statusBar()->setSelection(m_view->image()); } } void KisSelectionManager::selectionChanged() { m_view->updateGUI(); emit currentSelectionChanged(); } void KisSelectionManager::cut() { KisCutCopyActionFactory factory; factory.run(true, false, m_view); } void KisSelectionManager::copy() { KisCutCopyActionFactory factory; factory.run(false, false, m_view); } void KisSelectionManager::cutSharp() { KisCutCopyActionFactory factory; factory.run(true, true, m_view); } void KisSelectionManager::copySharp() { KisCutCopyActionFactory factory; factory.run(false, true, m_view); } void KisSelectionManager::copyMerged() { KisCopyMergedActionFactory factory; factory.run(m_view); } void KisSelectionManager::paste() { KisPasteActionFactory factory; factory.run(m_view); } void KisSelectionManager::pasteAt() { //XXX } void KisSelectionManager::pasteNew() { KisPasteNewActionFactory factory; factory.run(m_view); } void KisSelectionManager::selectAll() { KisSelectAllActionFactory factory; factory.run(m_view); } void KisSelectionManager::deselect() { KisDeselectActionFactory factory; factory.run(m_view); } void KisSelectionManager::invert() { if(m_invert) m_invert->trigger(); } void KisSelectionManager::reselect() { KisReselectActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertToVectorSelection() { KisSelectionToVectorActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertShapesToVectorSelection() { KisShapesToVectorSelectionActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertToShape() { KisSelectionToShapeActionFactory factory; factory.run(m_view); } void KisSelectionManager::clear() { KisClearActionFactory factory; factory.run(m_view); } void KisSelectionManager::fillForegroundColor() { KisFillActionFactory factory; factory.run("fg", m_view); } void KisSelectionManager::fillBackgroundColor() { KisFillActionFactory factory; factory.run("bg", m_view); } void KisSelectionManager::fillPattern() { KisFillActionFactory factory; factory.run("pattern", m_view); } void KisSelectionManager::fillForegroundColorOpacity() { KisFillActionFactory factory; factory.run("fg_opacity", m_view); } void KisSelectionManager::fillBackgroundColorOpacity() { KisFillActionFactory factory; factory.run("bg_opacity", m_view); } void KisSelectionManager::fillPatternOpacity() { KisFillActionFactory factory; factory.run("pattern_opacity", m_view); } void KisSelectionManager::copySelectionToNewLayer() { copy(); paste(); } void KisSelectionManager::cutToNewLayer() { cut(); paste(); } void KisSelectionManager::toggleDisplaySelection() { KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration); m_selectionDecoration->toggleVisibility(); m_toggleDisplaySelection->blockSignals(true); m_toggleDisplaySelection->setChecked(m_selectionDecoration->visible()); m_toggleDisplaySelection->blockSignals(false); emit displaySelectionChanged(); } bool KisSelectionManager::displaySelection() { return m_toggleDisplaySelection->isChecked(); } void KisSelectionManager::shapeSelectionChanged() { KoShapeManager* shapeManager = m_view->canvasBase()->globalShapeManager(); KoSelection * selection = shapeManager->selection(); QList selectedShapes = selection->selectedShapes(); KoShapeStroke* border = new KoShapeStroke(0, Qt::lightGray); Q_FOREACH (KoShape* shape, shapeManager->shapes()) { if (dynamic_cast(shape->parent())) { if (selectedShapes.contains(shape)) shape->setStroke(border); else shape->setStroke(0); } } m_view->updateGUI(); } void KisSelectionManager::imageResizeToSelection() { KisImageResizeToSelectionActionFactory factory; factory.run(m_view); } void KisSelectionManager::paintSelectedShapes() { KisImageWSP image = m_view->image(); if (!image) return; KisLayerSP layer = m_view->activeLayer(); if (!layer) return; QList shapes = m_view->canvasBase()->shapeManager()->selection()->selectedShapes(); KisPaintLayerSP paintLayer = new KisPaintLayer(image, i18n("Stroked Shapes"), OPACITY_OPAQUE_U8); KUndo2MagicString actionName = kundo2_i18n("Stroke Shapes"); m_adapter->beginMacro(actionName); m_adapter->addNode(paintLayer.data(), layer->parent().data(), layer.data()); KisFigurePaintingToolHelper helper(actionName, image, paintLayer.data(), m_view->resourceProvider()->resourceManager(), KisPainter::StrokeStyleBrush, KisPainter::FillStyleNone); Q_FOREACH (KoShape* shape, shapes) { QTransform matrix = shape->absoluteTransformation(0) * QTransform::fromScale(image->xRes(), image->yRes()); QPainterPath mapedOutline = matrix.map(shape->outline()); helper.paintPainterPath(mapedOutline); } m_adapter->endMacro(); } void KisSelectionManager::slotToggleSelectionDecoration() { KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration); KisSelectionDecoration::Mode mode = m_selectionDecoration->mode() ? KisSelectionDecoration::Ants : KisSelectionDecoration::Mask; m_selectionDecoration->setMode(mode); emit displaySelectionChanged(); } bool KisSelectionManager::showSelectionAsMask() const { if (m_selectionDecoration) { return m_selectionDecoration->mode() == KisSelectionDecoration::Mask; } return false; } void KisSelectionManager::slotStrokeSelection() { KisImageWSP image = m_view->image(); if (!image ) { return; } KisNodeSP currentNode = m_view->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); bool isVectorLayer = false; if (currentNode->inherits("KisShapeLayer")) { isVectorLayer = true; } QPointer dlg = new KisDlgStrokeSelection(image, m_view, isVectorLayer); if (dlg->exec() == QDialog::Accepted) { StrokeSelectionOptions params = dlg->getParams(); if (params.brushSelected){ KisStrokeBrushSelectionActionFactory factory; factory.run(m_view, params); } else { KisStrokeSelectionActionFactory factory; factory.run(m_view, params); } } delete dlg; } diff --git a/libs/ui/tool/kis_tool.cc b/libs/ui/tool/kis_tool.cc index 5eb901ff1d..29409c7f23 100644 --- a/libs/ui/tool/kis_tool.cc +++ b/libs/ui/tool/kis_tool.cc @@ -1,698 +1,698 @@ /* * Copyright (c) 2006, 2010 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include "opengl/kis_opengl_canvas2.h" #include "kis_canvas_resource_provider.h" #include "canvas/kis_canvas2.h" #include "kis_coordinates_converter.h" #include "filter/kis_filter_configuration.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_cursor.h" #include #include #include "kis_resources_snapshot.h" #include #include "kis_action_registry.h" #include "kis_tool_utils.h" struct Q_DECL_HIDDEN KisTool::Private { QCursor cursor; // the cursor that should be shown on tool activation. // From the canvas resources KoPattern* currentPattern{0}; KoAbstractGradient* currentGradient{0}; KoColor currentFgColor; KoColor currentBgColor; float currentExposure{1.0}; KisFilterConfigurationSP currentGenerator; QWidget* optionWidget{0}; ToolMode m_mode{HOVER_MODE}; bool m_isActive{false}; }; KisTool::KisTool(KoCanvasBase * canvas, const QCursor & cursor) : KoToolBase(canvas) , d(new Private) { d->cursor = cursor; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetCursorStyle())); connect(this, SIGNAL(isActiveChanged()), SLOT(resetCursorStyle())); KActionCollection *collection = this->canvas()->canvasController()->actionCollection(); if (!collection->action("toggle_fg_bg")) { QAction *toggleFgBg = KisActionRegistry::instance()->makeQAction("toggle_fg_bg", collection); collection->addAction("toggle_fg_bg", toggleFgBg); } if (!collection->action("reset_fg_bg")) { QAction *toggleFgBg = KisActionRegistry::instance()->makeQAction("reset_fg_bg", collection); collection->addAction("reset_fg_bg", toggleFgBg); } addAction("toggle_fg_bg", dynamic_cast(collection->action("toggle_fg_bg"))); addAction("reset_fg_bg", dynamic_cast(collection->action("reset_fg_bg"))); } KisTool::~KisTool() { delete d; } void KisTool::activate(ToolActivation toolActivation, const QSet &shapes) { Q_UNUSED(toolActivation); Q_UNUSED(shapes); resetCursorStyle(); if (!canvas()) return; if (!canvas()->resourceManager()) return; d->currentFgColor = canvas()->resourceManager()->resource(KoCanvasResourceManager::ForegroundColor).value(); d->currentBgColor = canvas()->resourceManager()->resource(KoCanvasResourceManager::BackgroundColor).value(); if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentPattern)) { d->currentPattern = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPattern).value(); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGradient)) { d->currentGradient = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGradient).value(); } KisPaintOpPresetSP preset = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && preset->settings()) { preset->settings()->activate(); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::HdrExposure)) { d->currentExposure = static_cast(canvas()->resourceManager()->resource(KisCanvasResourceProvider::HdrExposure).toDouble()); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGeneratorConfiguration)) { d->currentGenerator = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); } connect(actions().value("toggle_fg_bg"), SIGNAL(triggered()), SLOT(slotToggleFgBg()), Qt::UniqueConnection); connect(actions().value("reset_fg_bg"), SIGNAL(triggered()), SLOT(slotResetFgBg()), Qt::UniqueConnection); connect(image(), SIGNAL(sigUndoDuringStrokeRequested()), SLOT(requestUndoDuringStroke()), Qt::UniqueConnection); connect(image(), SIGNAL(sigStrokeCancellationRequested()), SLOT(requestStrokeCancellation()), Qt::UniqueConnection); connect(image(), SIGNAL(sigStrokeEndRequested()), SLOT(requestStrokeEnd()), Qt::UniqueConnection); d->m_isActive = true; emit isActiveChanged(); } void KisTool::deactivate() { bool result = true; result &= disconnect(image().data(), SIGNAL(sigUndoDuringStrokeRequested()), this, 0); result &= disconnect(image().data(), SIGNAL(sigStrokeCancellationRequested()), this, 0); result &= disconnect(image().data(), SIGNAL(sigStrokeEndRequested()), this, 0); result &= disconnect(actions().value("toggle_fg_bg"), 0, this, 0); result &= disconnect(actions().value("reset_fg_bg"), 0, this, 0); if (!result) { warnKrita << "WARNING: KisTool::deactivate() failed to disconnect" << "some signal connections. Your actions might be executed twice!"; } d->m_isActive = false; emit isActiveChanged(); } void KisTool::requestUndoDuringStroke() { /** * Default implementation just cancells the stroke */ requestStrokeCancellation(); } void KisTool::requestStrokeCancellation() { } void KisTool::requestStrokeEnd() { } void KisTool::canvasResourceChanged(int key, const QVariant & v) { switch (key) { case(KoCanvasResourceManager::ForegroundColor): d->currentFgColor = v.value(); break; case(KoCanvasResourceManager::BackgroundColor): d->currentBgColor = v.value(); break; case(KisCanvasResourceProvider::CurrentPattern): d->currentPattern = static_cast(v.value()); break; case(KisCanvasResourceProvider::CurrentGradient): d->currentGradient = static_cast(v.value()); break; case(KisCanvasResourceProvider::HdrExposure): d->currentExposure = static_cast(v.toDouble()); break; case(KisCanvasResourceProvider::CurrentGeneratorConfiguration): d->currentGenerator = static_cast(v.value()); break; case(KisCanvasResourceProvider::CurrentPaintOpPreset): emit statusTextChanged(v.value()->name()); break; case(KisCanvasResourceProvider::CurrentKritaNode): resetCursorStyle(); break; default: break; // Do nothing }; } void KisTool::updateSettingsViews() { } QPointF KisTool::widgetCenterInWidgetPixels() { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); const KisCoordinatesConverter *converter = kritaCanvas->coordinatesConverter(); return converter->flakeToWidget(converter->flakeCenterPoint()); } QPointF KisTool::convertDocumentToWidget(const QPointF& pt) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->coordinatesConverter()->documentToWidget(pt); } QPointF KisTool::convertToPixelCoord(KoPointerEvent *e) { if (!image()) return e->point; return image()->documentToPixel(e->point); } QPointF KisTool::convertToPixelCoord(const QPointF& pt) { if (!image()) return pt; return image()->documentToPixel(pt); } QPointF KisTool::convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset, bool useModifiers) { if (!image()) return e->point; KoSnapGuide *snapGuide = canvas()->snapGuide(); QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier); return image()->documentToPixel(pos); } QPointF KisTool::convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset) { if (!image()) return pt; KoSnapGuide *snapGuide = canvas()->snapGuide(); QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier); return image()->documentToPixel(pos); } QPoint KisTool::convertToIntPixelCoord(KoPointerEvent *e) { if (!image()) return e->point.toPoint(); return image()->documentToIntPixel(e->point); } QPointF KisTool::viewToPixel(const QPointF &viewCoord) const { if (!image()) return viewCoord; return image()->documentToPixel(canvas()->viewConverter()->viewToDocument(viewCoord)); } QRectF KisTool::convertToPt(const QRectF &rect) { if (!image()) return rect; QRectF r; //We add 1 in the following to the extreme coords because a pixel always has size r.setCoords(int(rect.left()) / image()->xRes(), int(rect.top()) / image()->yRes(), int(1 + rect.right()) / image()->xRes(), int(1 + rect.bottom()) / image()->yRes()); return r; } QPointF KisTool::pixelToView(const QPoint &pixelCoord) const { if (!image()) return pixelCoord; QPointF documentCoord = image()->pixelToDocument(pixelCoord); return canvas()->viewConverter()->documentToView(documentCoord); } QPointF KisTool::pixelToView(const QPointF &pixelCoord) const { if (!image()) return pixelCoord; QPointF documentCoord = image()->pixelToDocument(pixelCoord); return canvas()->viewConverter()->documentToView(documentCoord); } QRectF KisTool::pixelToView(const QRectF &pixelRect) const { if (!image()) return pixelRect; QPointF topLeft = pixelToView(pixelRect.topLeft()); QPointF bottomRight = pixelToView(pixelRect.bottomRight()); return QRectF(topLeft, bottomRight); } QPainterPath KisTool::pixelToView(const QPainterPath &pixelPolygon) const { QTransform matrix; qreal zoomX, zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes()); return matrix.map(pixelPolygon); } QPolygonF KisTool::pixelToView(const QPolygonF &pixelPath) const { QTransform matrix; qreal zoomX, zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes()); return matrix.map(pixelPath); } void KisTool::updateCanvasPixelRect(const QRectF &pixelRect) { canvas()->updateCanvas(convertToPt(pixelRect)); } void KisTool::updateCanvasViewRect(const QRectF &viewRect) { canvas()->updateCanvas(canvas()->viewConverter()->viewToDocument(viewRect)); } KisImageWSP KisTool::image() const { // For now, krita tools only work in krita, not for a krita shape. Krita shapes are for 2.1 KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (kisCanvas) { return kisCanvas->currentImage(); } return 0; } QCursor KisTool::cursor() const { return d->cursor; } void KisTool::notifyModified() const { if (image()) { image()->setModified(); } } KoPattern * KisTool::currentPattern() { return d->currentPattern; } KoAbstractGradient * KisTool::currentGradient() { return d->currentGradient; } KisPaintOpPresetSP KisTool::currentPaintOpPreset() { return canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); } KisNodeSP KisTool::currentNode() const { KisNodeSP node = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); return node; } KisNodeList KisTool::selectedNodes() const { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); return viewManager->nodeManager()->selectedNodes(); } KoColor KisTool::currentFgColor() { return d->currentFgColor; } KoColor KisTool::currentBgColor() { return d->currentBgColor; } KisImageWSP KisTool::currentImage() { return image(); } KisFilterConfigurationSP KisTool::currentGenerator() { return d->currentGenerator; } void KisTool::setMode(ToolMode mode) { d->m_mode = mode; } KisTool::ToolMode KisTool::mode() const { return d->m_mode; } KisTool::AlternateAction KisTool::actionToAlternateAction(ToolAction action) { KIS_ASSERT_RECOVER_RETURN_VALUE(action != Primary, Secondary); return (AlternateAction)action; } void KisTool::activatePrimaryAction() { resetCursorStyle(); } void KisTool::deactivatePrimaryAction() { resetCursorStyle(); } void KisTool::beginPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::beginPrimaryDoubleClickAction(KoPointerEvent *event) { beginPrimaryAction(event); } void KisTool::continuePrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } bool KisTool::primaryActionSupportsHiResEvents() const { return false; } void KisTool::activateAlternateAction(AlternateAction action) { Q_UNUSED(action); } void KisTool::deactivateAlternateAction(AlternateAction action) { Q_UNUSED(action); } void KisTool::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action) { beginAlternateAction(event, action); } void KisTool::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::endAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::mouseDoubleClickEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseTripleClickEvent(KoPointerEvent *event) { mouseDoubleClickEvent(event); } void KisTool::mousePressEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseReleaseEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseMoveEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::deleteSelection() { KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); - viewManager->blockUntillOperationsFinished(image()); + viewManager->blockUntilOperationsFinished(image()); if (!KisToolUtils::clearImage(image(), resources->currentNode(), resources->activeSelection())) { KoToolBase::deleteSelection(); } } void KisTool::setupPaintAction(KisRecordedPaintAction* action) { action->setPaintColor(currentFgColor()); action->setBackgroundColor(currentBgColor()); } QWidget* KisTool::createOptionWidget() { d->optionWidget = new QLabel(i18n("No options")); d->optionWidget->setObjectName("SpecialSpacer"); return d->optionWidget; } #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #define PROGRAM_VERTEX_ATTRIBUTE 0 void KisTool::paintToolOutline(QPainter* painter, const QPainterPath &path) { KisOpenGLCanvas2 *canvasWidget = dynamic_cast(canvas()->canvasWidget()); if (canvasWidget) { painter->beginNativePainting(); canvasWidget->paintToolOutline(path); painter->endNativePainting(); } else { painter->setCompositionMode(QPainter::RasterOp_SourceXorDestination); painter->setPen(QColor(128, 255, 128)); painter->drawPath(path); } } void KisTool::resetCursorStyle() { useCursor(d->cursor); } bool KisTool::overrideCursorIfNotEditable() { // override cursor for canvas iff this tool is active // and we can't paint on the active layer if (isActive()) { KisNodeSP node = currentNode(); if (node && !node->isEditable()) { canvas()->setCursor(Qt::ForbiddenCursor); return true; } } return false; } bool KisTool::isActive() const { return d->m_isActive; } void KisTool::slotToggleFgBg() { KoCanvasResourceManager* resourceManager = canvas()->resourceManager(); KoColor newFg = resourceManager->backgroundColor(); KoColor newBg = resourceManager->foregroundColor(); /** * NOTE: Some of color selectors do not differentiate foreground * and background colors, so if one wants them to end up * being set up to foreground color, it should be set the * last. */ resourceManager->setBackgroundColor(newBg); resourceManager->setForegroundColor(newFg); } void KisTool::slotResetFgBg() { KoCanvasResourceManager* resourceManager = canvas()->resourceManager(); // see a comment in slotToggleFgBg() resourceManager->setBackgroundColor(KoColor(Qt::white, KoColorSpaceRegistry::instance()->rgb8())); resourceManager->setForegroundColor(KoColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8())); } void KisTool::setCurrentNodeLocked(bool locked) { if (currentNode()) { currentNode()->setSystemLocked(locked, false); } } bool KisTool::nodeEditable() { KisNodeSP node = currentNode(); if (!node) { return false; } bool nodeEditable = node->isEditable(); if (!nodeEditable) { KisCanvas2 * kiscanvas = static_cast(canvas()); QString message; if (!node->visible() && node->userLocked()) { message = i18n("Layer is locked and invisible."); } else if (node->userLocked()) { message = i18n("Layer is locked."); } else if(!node->visible()) { message = i18n("Layer is invisible."); } else { message = i18n("Group not editable."); } kiscanvas->viewManager()->showFloatingMessage(message, KisIconUtils::loadIcon("object-locked")); } return nodeEditable; } bool KisTool::selectionEditable() { KisCanvas2 * kisCanvas = static_cast(canvas()); KisViewManager * view = kisCanvas->viewManager(); bool editable = view->selectionEditable(); if (!editable) { KisCanvas2 * kiscanvas = static_cast(canvas()); kiscanvas->viewManager()->showFloatingMessage(i18n("Local selection is locked."), KisIconUtils::loadIcon("object-locked")); } return editable; } void KisTool::listenToModifiers(bool listen) { Q_UNUSED(listen); } bool KisTool::listeningToModifiers() { return false; } diff --git a/libs/widgets/KoColorSetWidget.cpp b/libs/widgets/KoColorSetWidget.cpp index c8e9758060..2439ef6e1b 100644 --- a/libs/widgets/KoColorSetWidget.cpp +++ b/libs/widgets/KoColorSetWidget.cpp @@ -1,286 +1,331 @@ /* This file is part of the KDE project Copyright (c) 2007, 2012 C. Boemann Copyright (c) 2007-2008 Fredy Yanardi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColorSetWidget.h" #include "KoColorSetWidget_p.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 void KoColorSetWidget::KoColorSetWidgetPrivate::fillColors() { delete colorSetContainer; colorSetContainer = new QWidget(); - colorSetLayout = new QGridLayout(); + colorSetLayout = new QVBoxLayout(); colorSetLayout->setMargin(3); colorSetLayout->setSpacing(0); // otherwise the use can click where there is none colorSetContainer->setBackgroundRole(QPalette::Dark); + int patchSize = 12; + int columns = 16; if (colorSet) { columns = colorSet->columnCount(); } - for(int i = 0; isetColumnMinimumWidth(i, 12); - } - colorSetContainer->setMinimumWidth(columns*12+6); + colorSetContainer->setMinimumWidth(columns*patchSize+6); colorSetContainer->setLayout(colorSetLayout); patchWidgetList.clear(); colornames.clear(); colorNameCmb->clear(); + QWidget *defaultGroupContainer = new QWidget(); + QGridLayout *colorGroupLayout = new QGridLayout(); + for(int i = 0; isetColumnMinimumWidth(i, patchSize); + } + defaultGroupContainer->setMinimumWidth(columns*patchSize); + defaultGroupContainer->setMaximumWidth(columns*patchSize); + colorGroupLayout->setSpacing(0); + colorGroupLayout->setMargin(0); + defaultGroupContainer->setLayout(colorGroupLayout); if (colorSet) { - for( int i = 0, p= 0; i < colorSet->nColors(); i++) { + for( quint32 i = 0, p= 0; i < colorSet->nColorsGroup(); i++) { KoColorPatch *patch = new KoColorPatch(colorSetContainer); patch->setFrameStyle(QFrame::Plain | QFrame::Box); patch->setLineWidth(1); - patch->setColor(colorSet->getColor(i).color); - patch->setToolTip(colorSet->getColor(i).name); + KoColorSetEntry c = colorSet->getColorGlobal(i); + patch->setColor(c.color); + patch->setToolTip(c.name); connect(patch, SIGNAL(triggered(KoColorPatch *)), thePublic, SLOT(colorTriggered(KoColorPatch *))); - colorSetLayout->addWidget(patch, p/columns, p%columns); + colorGroupLayout->addWidget(patch, p/columns, p%columns); patch->setDisplayRenderer(displayRenderer); patchWidgetList.append(patch); - colornames.append(colorSet->getColor(i).name); + colornames.append(c.name); QPixmap colorsquare = QPixmap(12,12); - colorsquare.fill(colorSet->getColor(i).color.toQColor()); - colorNameCmb->addItem(QIcon(colorsquare), colorSet->getColor(i).name); + colorsquare.fill(c.color.toQColor()); + colorNameCmb->addItem(QIcon(colorsquare), c.name); ++p; } + colorSetLayout->addWidget(defaultGroupContainer); + Q_FOREACH(QString groupName, colorSet->getGroupNames()) { + QGroupBox *groupbox = new QGroupBox(); + groupbox->setTitle(groupName); + QGridLayout *groupLayout = new QGridLayout(); + for(int i = 0; isetColumnMinimumWidth(i, patchSize); + } + groupLayout->setSpacing(0); + groupLayout->setMargin(0); + groupbox->setMinimumWidth(columns*patchSize); + groupbox->setMaximumWidth(columns*patchSize); + groupbox->setLayout(groupLayout); + groupbox->setFlat(true); + for( quint32 i = 0, p= 0; i < colorSet->nColorsGroup(groupName); i++) { + KoColorPatch *patch = new KoColorPatch(colorSetContainer); + patch->setFrameStyle(QFrame::Plain | QFrame::Box); + patch->setLineWidth(1); + KoColorSetEntry c = colorSet->getColorGroup(i, groupName); + patch->setColor(c.color); + patch->setToolTip(c.name); + connect(patch, SIGNAL(triggered(KoColorPatch *)), thePublic, SLOT(colorTriggered(KoColorPatch *))); + groupLayout->addWidget(patch, p/columns, p%columns); + patch->setDisplayRenderer(displayRenderer); + patchWidgetList.append(patch); + colornames.append(c.name); + QPixmap colorsquare = QPixmap(12,12); + colorsquare.fill(c.color.toQColor()); + colorNameCmb->addItem(QIcon(colorsquare), c.name); + ++p; + } + colorSetLayout->addWidget(groupbox); + } } scrollArea->setWidget(colorSetContainer); connect(colorNameCmb, SIGNAL(activated(QString)), thePublic, SLOT(setColorFromString(QString)), Qt::UniqueConnection); } void KoColorSetWidget::KoColorSetWidgetPrivate::addRemoveColors() { KoResourceServer* srv = KoResourceServerProvider::instance()->paletteServer(); QList palettes = srv->resources(); Q_ASSERT(colorSet); KoEditColorSetDialog *dlg = new KoEditColorSetDialog(palettes, colorSet->name(), thePublic); if (dlg->exec() == KoDialog::Accepted ) { // always reload the color set KoColorSet * cs = dlg->activeColorSet(); // check if the selected colorset is predefined if( cs && !palettes.contains( cs ) ) { int i = 1; QFileInfo fileInfo; QString savePath = srv->saveLocation(); do { - fileInfo.setFile( savePath + QString("%1.gpl").arg( i++, 4, 10, QChar('0') ) ); + fileInfo.setFile(savePath + QString("%1.%2").arg(i++, 4, 10, QChar('0')).arg(colorSet->defaultFileExtension())); } while (fileInfo.exists()); cs->setFilename( fileInfo.filePath() ); cs->setValid( true ); // add new colorset to predefined colorsets if (!srv->addResource(cs)) { delete cs; cs = 0; } } if (cs) { thePublic->setColorSet(cs); } } delete dlg; } void KoColorSetWidget::KoColorSetWidgetPrivate::addRecent(const KoColor &color) { if(numRecents<6) { recentPatches[numRecents] = new KoColorPatch(thePublic); recentPatches[numRecents]->setFrameShape(QFrame::StyledPanel); recentPatches[numRecents]->setDisplayRenderer(displayRenderer); recentsLayout->insertWidget(numRecents+1, recentPatches[numRecents]); connect(recentPatches[numRecents], SIGNAL(triggered(KoColorPatch *)), thePublic, SLOT(colorTriggered(KoColorPatch *))); numRecents++; } // shift colors to the right for (int i = numRecents- 1; i >0; i--) { recentPatches[i]->setColor(recentPatches[i-1]->color()); } //Finally set the recent color recentPatches[0]->setColor(color); } void KoColorSetWidget::KoColorSetWidgetPrivate::activateRecent(int i) { KoColor color = recentPatches[i]->color(); while (i >0) { recentPatches[i]->setColor(recentPatches[i-1]->color()); i--; } recentPatches[0]->setColor(color); } KoColorSetWidget::KoColorSetWidget(QWidget *parent) : QFrame(parent) ,d(new KoColorSetWidgetPrivate()) { d->thePublic = this; d->colorSet = 0; d->firstShowOfContainer = true; d->mainLayout = new QVBoxLayout(); d->mainLayout->setMargin(4); d->mainLayout->setSpacing(2); d->colorSetContainer = 0; d->numRecents = 0; d->recentsLayout = new QHBoxLayout(); d->mainLayout->addLayout(d->recentsLayout); d->recentsLayout->setMargin(0); d->recentsLayout->addWidget(new QLabel(i18n("Recent:"))); d->recentsLayout->addStretch(1); KoColor color(KoColorSpaceRegistry::instance()->rgb8()); color.fromQColor(QColor(128,0,0)); d->addRecent(color); d->scrollArea = new QScrollArea(); d->scrollArea->setBackgroundRole(QPalette::Dark); d->mainLayout->addWidget(d->scrollArea); d->colorNameCmb = new QComboBox(this); d->colorNameCmb->setEditable(true); d->colorNameCmb->setInsertPolicy(QComboBox::NoInsert); d->mainLayout->addWidget(d->colorNameCmb); d->fillColors(); d->addRemoveButton = new QToolButton(this); d->addRemoveButton->setText(i18n("Add / Remove Colors...")); d->addRemoveButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); connect(d->addRemoveButton, SIGNAL(clicked()), SLOT(addRemoveColors())); d->mainLayout->addWidget(d->addRemoveButton); setLayout(d->mainLayout); KoColorSet *colorSet = new KoColorSet(); d->colorSet = colorSet; d->fillColors(); } KoColorSetWidget::~KoColorSetWidget() { KoResourceServer* srv = KoResourceServerProvider::instance()->paletteServer(); QList palettes = srv->resources(); if (!palettes.contains(d->colorSet)) { delete d->colorSet; } delete d; } void KoColorSetWidget::KoColorSetWidgetPrivate::colorTriggered(KoColorPatch *patch) { int i; emit thePublic->colorChanged(patch->color(), true); colorNameCmb->setCurrentIndex(colornames.indexOf(QRegExp(patch->toolTip()+"|Fixed"))); for(i = 0; i color()); } void KoColorSetWidget::KoColorSetWidgetPrivate::setColorFromString(QString s) { int i = colornames.indexOf(QRegExp(s+"|Fixed")); i = qMax(i,0); colorTriggered(patchWidgetList.at(i)); } void KoColorSetWidget::setColorSet(KoColorSet *colorSet) { if (colorSet == d->colorSet) return; KoResourceServer* srv = KoResourceServerProvider::instance()->paletteServer(); QList palettes = srv->resources(); if (!palettes.contains(d->colorSet)) { delete d->colorSet; } d->colorSet = colorSet; d->fillColors(); } KoColorSet* KoColorSetWidget::colorSet() { return d->colorSet; } void KoColorSetWidget::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { d->displayRenderer = displayRenderer; Q_FOREACH(KoColorPatch *p, d->patchWidgetList) { p->setDisplayRenderer(displayRenderer); } for (int i=0; i<6; i++) { if (d->recentPatches[i]) { d->recentPatches[i]->setDisplayRenderer(displayRenderer); } } } } void KoColorSetWidget::resizeEvent(QResizeEvent *event) { emit widgetSizeChanged(event->size()); QFrame::resizeEvent(event); } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoColorSetWidget.cpp" diff --git a/libs/widgets/KoColorSetWidget_p.h b/libs/widgets/KoColorSetWidget_p.h index 867dadf774..1fae481b7f 100644 --- a/libs/widgets/KoColorSetWidget_p.h +++ b/libs/widgets/KoColorSetWidget_p.h @@ -1,78 +1,78 @@ /* This file is part of the KDE project Copyright (c) 2007, 2012 C. Boemann Copyright (c) 2007-2008 Fredy Yanardi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KoColorSetWidget_p_h #define KoColorSetWidget_p_h #include "KoColorSetWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KoColorPatch; class Q_DECL_HIDDEN KoColorSetWidget::KoColorSetWidgetPrivate { public: KoColorSetWidget *thePublic; QPointer colorSet; QTimer m_timer; QVBoxLayout *mainLayout; bool firstShowOfContainer; QWidget *colorSetContainer; QScrollArea *scrollArea; - QGridLayout *colorSetLayout; + QVBoxLayout *colorSetLayout; QHBoxLayout *recentsLayout; KoColorPatch *recentPatches[6]; QToolButton *addRemoveButton; QComboBox *colorNameCmb; QStringList colornames; int numRecents; void colorTriggered(KoColorPatch *patch); void addRecent(const KoColor &); void activateRecent(int i); void fillColors(); void addRemoveColors(); void setColorFromString(QString s); QList patchWidgetList; const KoColorDisplayRendererInterface *displayRenderer; }; #endif diff --git a/libs/widgets/KoEditColorSetDialog.cpp b/libs/widgets/KoEditColorSetDialog.cpp index 239fc46a1f..01bcb73b90 100644 --- a/libs/widgets/KoEditColorSetDialog.cpp +++ b/libs/widgets/KoEditColorSetDialog.cpp @@ -1,239 +1,243 @@ /* This file is part of the KDE project * Copyright (C) 2007 Fredy Yanardi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoEditColorSetDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include // debug #include KoEditColorSetWidget::KoEditColorSetWidget(const QList &palettes, const QString &activePalette, QWidget *parent) : QWidget(parent), m_colorSets(palettes), m_gridLayout(0), m_activeColorSet(0), m_activePatch(0), m_initialColorSetCount(palettes.count()), m_activeColorSetRequested(false) { widget.setupUi(this); foreach (KoColorSet *colorSet, m_colorSets) { - colorSet->load(); + //colorSet->load(); resources are loaded at startup... widget.selector->addItem(colorSet->name()); } connect(widget.selector, SIGNAL(currentIndexChanged(int)), this, SLOT(setActiveColorSet(int))); // A widget that shows all colors from active palette // FIXME no need to handcode the QScrollArea if designer can add QScrollArea (Qt 4.4?) m_scrollArea = new QScrollArea(widget.patchesFrame); int index = 0; foreach (KoColorSet *set, m_colorSets) { if (set->name() == activePalette) { m_activeColorSet = set; index = widget.selector->findText(set->name()); widget.selector->setCurrentIndex(index); } } if (!m_activeColorSet && !palettes.isEmpty()) { m_activeColorSet = palettes.first(); index = widget.selector->findText(m_activeColorSet->name()); } int columns = 16; if(m_activeColorSet) { columns = m_activeColorSet->columnCount(); if (columns==0){ columns = 16; } } m_scrollArea->setMinimumWidth(columns*(12+2)); QHBoxLayout *layout = new QHBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_scrollArea); widget.patchesFrame->setLayout(layout); widget.add->setIcon(koIcon("list-add")); widget.remove->setIcon(koIcon("list-remove")); widget.open->setIcon(koIcon("document-open")); widget.save->setIcon(koIcon("document-save")); setEnabled(m_activeColorSet != 0); setActiveColorSet(index); widget.remove->setEnabled(false); // initially no color selected connect(widget.add, SIGNAL(clicked()), this, SLOT(addColor())); connect(widget.remove, SIGNAL(clicked()), this, SLOT(removeColor())); connect(widget.open, SIGNAL(clicked()), this, SLOT(open())); connect(widget.save, SIGNAL(clicked()), this, SLOT(save())); } KoEditColorSetWidget::~KoEditColorSetWidget() { // only delete new color sets uint colorSetCount = m_colorSets.count(); for( uint i = m_initialColorSetCount; i < colorSetCount; ++i ) { KoColorSet * cs = m_colorSets[i]; // if the active color set was requested by activeColorSet() // the caller takes ownership and then we do not delete it here if( cs == m_activeColorSet && m_activeColorSetRequested ) continue; delete cs; } } void KoEditColorSetWidget::setActiveColorSet(int index) { if (m_gridLayout) { + qDeleteAll(m_gridLayout->children()); delete m_gridLayout; m_activePatch = 0; } QWidget *wdg = new QWidget(m_scrollArea); m_gridLayout = new QGridLayout(); m_gridLayout->setMargin(0); m_gridLayout->setSpacing(2); m_activeColorSet = m_colorSets.value(index); setEnabled(m_activeColorSet != 0); int columns = 16; + if (m_activeColorSet) { columns = m_activeColorSet->columnCount(); if (columns==0){columns=16;} widget.remove->setEnabled(false); - for (int i = 0; i < m_activeColorSet->nColors(); i++) { + for (quint32 i = 0; i < m_activeColorSet->nColors(); i++) { KoColorPatch *patch = new KoColorPatch(widget.patchesFrame); - patch->setColor(m_activeColorSet->getColor(i).color); - patch->setToolTip(m_activeColorSet->getColor(i).name); + KoColorSetEntry c = m_activeColorSet->getColorGlobal(i); + patch->setColor(c.color); + patch->setToolTip(c.name); connect(patch, SIGNAL(triggered(KoColorPatch *)), this, SLOT(setTextLabel(KoColorPatch *))); m_gridLayout->addWidget(patch, i/columns, i%columns); } } m_scrollArea->setMinimumWidth(columns*(12+2)); wdg->setLayout(m_gridLayout); m_scrollArea->setWidget(wdg); } void KoEditColorSetWidget::setTextLabel(KoColorPatch *patch) { widget.colorName->setText(patch->toolTip()); if (m_activePatch) { m_activePatch->setFrameShape(QFrame::NoFrame); m_activePatch->setFrameShadow(QFrame::Plain); } m_activePatch = patch; m_activePatch->setFrameShape(QFrame::Panel); m_activePatch->setFrameShadow(QFrame::Raised); widget.remove->setEnabled(true); } void KoEditColorSetWidget::addColor() { QColor color; color = QColorDialog::getColor(color); if (color.isValid()) { KoColorSetEntry newEntry; newEntry.color = KoColor(color, KoColorSpaceRegistry::instance()->rgb8()); newEntry.name = QInputDialog::getText(this, i18n("Add Color To Palette"), i18n("Color name:")); KoColorPatch *patch = new KoColorPatch(widget.patchesFrame); patch->setColor(newEntry.color); patch->setToolTip(newEntry.name); connect(patch, SIGNAL(triggered(KoColorPatch *)), this, SLOT(setTextLabel(KoColorPatch *))); Q_ASSERT(m_gridLayout); Q_ASSERT(m_activeColorSet); m_gridLayout->addWidget(patch, m_activeColorSet->nColors()/m_activeColorSet->columnCount(), m_activeColorSet->nColors()%m_activeColorSet->columnCount()); m_activeColorSet->add(newEntry); } } void KoEditColorSetWidget::removeColor() { Q_ASSERT(m_activeColorSet); - for (int i = 0; i < m_activeColorSet->nColors(); i++) { - if (m_activePatch->color() == m_activeColorSet->getColor(i).color) { - m_activeColorSet->remove(m_activeColorSet->getColor(i)); + for (quint32 i = 0; i < m_activeColorSet->nColors(); i++) { + KoColorSetEntry c = m_activeColorSet->getColorGlobal(i); + if (m_activePatch->color() == c.color) { + m_activeColorSet->removeAt(i); setActiveColorSet(widget.selector->currentIndex()); break; } } } void KoEditColorSetWidget::open() { Q_ASSERT(m_activeColorSet); KoFileDialog dialog(this, KoFileDialog::OpenFile, "OpenColorSet"); dialog.setDefaultDir(m_activeColorSet->filename()); dialog.setMimeTypeFilters(QStringList() << "application/x-gimp-color-palette"); QString fileName = dialog.filename(); KoColorSet *colorSet = new KoColorSet(fileName); colorSet->load(); m_colorSets.append(colorSet); widget.selector->addItem(colorSet->name()); widget.selector->setCurrentIndex(widget.selector->count() - 1); } void KoEditColorSetWidget::save() { Q_ASSERT(m_activeColorSet); if (!m_activeColorSet->save()) KMessageBox::error(0, i18n("Cannot write to palette file %1. Maybe it is read-only. ", m_activeColorSet->filename()), i18n("Palette")); } KoColorSet *KoEditColorSetWidget::activeColorSet() { m_activeColorSetRequested = true; return m_activeColorSet; } KoEditColorSetDialog::KoEditColorSetDialog(const QList &palettes, const QString &activePalette, QWidget *parent) : KoDialog(parent) { ui = new KoEditColorSetWidget(palettes, activePalette, this); setMainWidget(ui); setCaption(i18n("Add/Remove Colors")); enableButton(KoDialog::Ok, ui->isEnabled()); } KoEditColorSetDialog::~KoEditColorSetDialog() { delete ui; } KoColorSet *KoEditColorSetDialog::activeColorSet() { return ui->activeColorSet(); } diff --git a/libs/widgets/KoIconToolTip.cpp b/libs/widgets/KoIconToolTip.cpp index 0aa4cebefd..989ea0d79c 100644 --- a/libs/widgets/KoIconToolTip.cpp +++ b/libs/widgets/KoIconToolTip.cpp @@ -1,48 +1,56 @@ /* This file is part of the KDE project Copyright (c) 1999 Carsten Pfeiffer Copyright (c) 2002 Igor Jansen Copyright (c) 2007 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoIconToolTip.h" #include #include #include // #include +KoIconToolTip::KoIconToolTip() +{ +} + +KoIconToolTip::~KoIconToolTip() +{ +} + QTextDocument *KoIconToolTip::createDocument( const QModelIndex &index ) { QTextDocument *doc = new QTextDocument( this ); QImage thumb = index.data( KoResourceModel::LargeThumbnailRole ).value(); doc->addResource( QTextDocument::ImageResource, QUrl( "data:thumbnail" ), thumb ); QString name = index.data( Qt::DisplayRole ).toString(); - const QString image = QString( "" ); - const QString body = QString( "

%1

" ).arg( name ) + image; + const QString image = QString( "
" ); + const QString body = QString( "

%1

%2" ).arg( name ).arg(image); const QString html = QString( "%1" ).arg( body ); doc->setHtml( html ); doc->setTextWidth( qMin( doc->size().width(), qreal(500.0) ) ); return doc; } diff --git a/libs/widgets/KoIconToolTip.h b/libs/widgets/KoIconToolTip.h index ebbd83224b..ccfb4d33ff 100644 --- a/libs/widgets/KoIconToolTip.h +++ b/libs/widgets/KoIconToolTip.h @@ -1,40 +1,42 @@ /* This file is part of the KDE project Copyright (c) 1999 Carsten Pfeiffer (pfeiffer@kde.org) Copyright (c) 2002 Igor Jansen (rm@kde.org) 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 KOICONTOOLTIP_H #define KOICONTOOLTIP_H #include "KoItemToolTip.h" -class KoIconToolTip: public KoItemToolTip +#include "kritawidgets_export.h" + +class KRITAWIDGETS_EXPORT KoIconToolTip: public KoItemToolTip { public: - KoIconToolTip() {} - virtual ~KoIconToolTip() {} + KoIconToolTip(); + virtual ~KoIconToolTip(); protected: virtual QTextDocument *createDocument( const QModelIndex &index ); private: typedef KoItemToolTip super; }; #endif // KOICONTOOLTIP_H diff --git a/libs/widgets/KoResourceServerProvider.cpp b/libs/widgets/KoResourceServerProvider.cpp index 09d3bca72c..f34cd582eb 100644 --- a/libs/widgets/KoResourceServerProvider.cpp +++ b/libs/widgets/KoResourceServerProvider.cpp @@ -1,242 +1,242 @@ /* This file is part of the KDE project Copyright (c) 1999 Matthias Elter Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Sven Langkamp Copyright (C) 2011 Srikanth Tiyyagura 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 "KoResourceServerProvider.h" #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoResourcePaths.h" #include using namespace std; class GradientResourceServer : public KoResourceServer { public: GradientResourceServer(const QString& type, const QString& extensions) : KoResourceServer(type, extensions) , m_foregroundToTransparent(0) , m_foregroundToBackground(0) { insertSpecialGradients(); } void insertSpecialGradients() { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); QList stops; KoStopGradient* gradient = new KoStopGradient(); gradient->setType(QGradient::LinearGradient); gradient->setName("Foreground to Transparent"); stops << KoGradientStop(0.0, KoColor(Qt::black, cs)) << KoGradientStop(1.0, KoColor(QColor(0, 0, 0, 0), cs)); gradient->setStops(stops); gradient->setValid(true); gradient->setPermanent(true); addResource(gradient, false, true); m_foregroundToTransparent = gradient; gradient = new KoStopGradient(); gradient->setType(QGradient::LinearGradient); gradient->setName("Foreground to Background"); stops.clear(); stops << KoGradientStop(0.0, KoColor(Qt::black, cs)) << KoGradientStop(1.0, KoColor(Qt::white, cs)); gradient->setStops(stops); gradient->setValid(true); gradient->setPermanent(true); addResource(gradient, false, true); m_foregroundToBackground = gradient; } private: friend class KoResourceBundle; KoAbstractGradient* createResource( const QString & filename ) override { QString fileExtension; int index = filename.lastIndexOf('.'); if (index != -1) fileExtension = filename.mid(index).toLower(); KoAbstractGradient* grad = 0; if(fileExtension == ".svg" || fileExtension == ".kgr") grad = new KoStopGradient(filename); else if(fileExtension == ".ggr" ) grad = new KoSegmentGradient(filename); return grad; } QList< KoAbstractGradient* > sortedResources() override { QList< KoAbstractGradient* > resources = KoResourceServer::sortedResources(); QList< KoAbstractGradient* > sorted; if (m_foregroundToTransparent && resources.contains(m_foregroundToTransparent)) { sorted.append(resources.takeAt(resources.indexOf(m_foregroundToTransparent))); } if (m_foregroundToBackground && resources.contains(m_foregroundToBackground)) { sorted.append(resources.takeAt(resources.indexOf(m_foregroundToBackground))); } return sorted + resources; } KoAbstractGradient* m_foregroundToTransparent; KoAbstractGradient* m_foregroundToBackground; }; KoResourceLoaderThread::KoResourceLoaderThread(KoResourceServerBase * server) : QThread() , m_server(server) { m_fileNames = m_server->fileNames(); QStringList fileNames = m_server->blackListedFiles(); if (!fileNames.isEmpty()) { foreach (const QString &s, fileNames) { if (m_fileNames.contains(s)) { m_fileNames.removeAll(s); } } } connect(qApp, SIGNAL(aboutToQuit()), SLOT(barrier())); } KoResourceLoaderThread::~KoResourceLoaderThread() { } void KoResourceLoaderThread::loadSynchronously() { m_server->loadResources(m_fileNames); } void KoResourceLoaderThread::run() { m_server->loadResources(m_fileNames); } void KoResourceLoaderThread::barrier() { if(isRunning()) { wait(); } } struct Q_DECL_HIDDEN KoResourceServerProvider::Private { KoResourceServer* patternServer; KoResourceServer* gradientServer; KoResourceServer* paletteServer; KoResourceLoaderThread *paletteThread; KoResourceLoaderThread *gradientThread; KoResourceLoaderThread *patternThread; }; KoResourceServerProvider::KoResourceServerProvider() : d(new Private) { d->patternServer = new KoResourceServerSimpleConstruction("ko_patterns", "*.pat:*.jpg:*.gif:*.png:*.tif:*.xpm:*.bmp" ); if (!QFileInfo(d->patternServer->saveLocation()).exists()) { QDir().mkpath(d->patternServer->saveLocation()); } d->patternThread = new KoResourceLoaderThread(d->patternServer); d->patternThread->loadSynchronously(); // if (qApp->applicationName().contains(QLatin1String("test"), Qt::CaseInsensitive)) { // d->patternThread->barrier(); // } d->gradientServer = new GradientResourceServer("ko_gradients", "*.kgr:*.svg:*.ggr"); if (!QFileInfo(d->gradientServer->saveLocation()).exists()) { QDir().mkpath(d->gradientServer->saveLocation()); } d->gradientThread = new KoResourceLoaderThread(d->gradientServer); d->gradientThread->loadSynchronously(); // if (qApp->applicationName().contains(QLatin1String("test"), Qt::CaseInsensitive)) { // d->gradientThread->barrier(); // } - d->paletteServer = new KoResourceServerSimpleConstruction("ko_palettes", "*.gpl:*.pal:*.act:*.aco:*.css:*.colors;*.xml"); + d->paletteServer = new KoResourceServerSimpleConstruction("ko_palettes", "*.kpl:*.gpl:*.pal:*.act:*.aco:*.css:*.colors:*.xml:*.sbz"); if (!QFileInfo(d->paletteServer->saveLocation()).exists()) { QDir().mkpath(d->paletteServer->saveLocation()); } d->paletteThread = new KoResourceLoaderThread(d->paletteServer); d->paletteThread->loadSynchronously(); // if (qApp->applicationName().contains(QLatin1String("test"), Qt::CaseInsensitive)) { // d->paletteThread->barrier(); // } } KoResourceServerProvider::~KoResourceServerProvider() { delete d->patternThread; delete d->gradientThread; delete d->paletteThread; delete d->patternServer; delete d->gradientServer; delete d->paletteServer; delete d; } Q_GLOBAL_STATIC(KoResourceServerProvider, s_instance); KoResourceServerProvider* KoResourceServerProvider::instance() { return s_instance; } KoResourceServer* KoResourceServerProvider::patternServer(bool block) { if (block) d->patternThread->barrier(); return d->patternServer; } KoResourceServer* KoResourceServerProvider::gradientServer(bool block) { if (block) d->gradientThread->barrier(); return d->gradientServer; } KoResourceServer* KoResourceServerProvider::paletteServer(bool block) { if (block) d->paletteThread->barrier(); return d->paletteServer; } diff --git a/plugins/dockers/animation/CMakeLists.txt b/plugins/dockers/animation/CMakeLists.txt index 11e884cce7..874c62f6f1 100644 --- a/plugins/dockers/animation/CMakeLists.txt +++ b/plugins/dockers/animation/CMakeLists.txt @@ -1,57 +1,57 @@ if (NOT WIN32 AND NOT APPLE) add_subdirectory(tests) endif() include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) set(KRITA_ANIMATIONDOCKER_SOURCES animation_dockers.cpp animation_docker.cpp timeline_docker.cpp onion_skins_docker.cpp timeline_layers_header.cpp timeline_ruler_header.cpp kis_time_based_item_model.cpp timeline_frames_model.cpp timeline_frames_view.cpp timeline_frames_item_delegate.cpp timeline_frames_index_converter.cpp timeline_node_list_keeper.cpp timeline_color_scheme.cpp kis_draggable_tool_button.cpp kis_zoom_button.cpp kis_animation_utils.cpp kis_custom_modifiers_catcher.cpp kis_equalizer_column.cpp kis_equalizer_slider.cpp kis_equalizer_button.cpp kis_equalizer_widget.cpp kis_animation_curve_docker.cpp kis_animation_curves_model.cpp kis_animation_curves_view.cpp kis_animation_curves_value_ruler.cpp kis_animation_curves_keyframe_delegate.cpp kis_animation_curve_channel_list_model.cpp kis_animation_curve_channel_list_delegate.cpp ) ki18n_wrap_ui(KRITA_ANIMATIONDOCKER_SOURCES wdg_animation.ui onion_skins_docker.ui wdg_animation_curves.ui ) add_library(kritaanimationdocker MODULE ${KRITA_ANIMATIONDOCKER_SOURCES}) generate_export_header(kritaanimationdocker BASE_NAME kritaanimationdocker EXPORT_MACRO_NAME KRITAANIMATIONDOCKER_EXPORT) -target_link_libraries(kritaanimationdocker kritaui) +target_link_libraries(kritaanimationdocker kritaui kritawidgets) install(TARGETS kritaanimationdocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp index 47c821f734..38c90dc390 100644 --- a/plugins/dockers/animation/timeline_frames_model.cpp +++ b/plugins/dockers/animation/timeline_frames_model.cpp @@ -1,707 +1,724 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_frames_model.h" #include #include #include #include #include +#include #include "kis_layer.h" #include "kis_config.h" #include "kis_global.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_undo_adapter.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" #include "kis_signal_compressor.h" #include "kis_signal_compressor_with_param.h" #include "kis_keyframe_channel.h" #include "kundo2command.h" #include "kis_post_execution_undo_adapter.h" #include #include "kis_animation_utils.h" #include "timeline_color_scheme.h" #include "kis_node_model.h" #include "kis_projection_leaf.h" #include "kis_time_range.h" #include "kis_node_view_color_scheme.h" #include "krita_utils.h" #include struct TimelineFramesModel::Private { Private() : activeLayerIndex(0), dummiesFacade(0), needFinishInsertRows(false), needFinishRemoveRows(false), updateTimer(200, KisSignalCompressor::FIRST_INACTIVE), parentOfRemovedNode(0) {} int activeLayerIndex; QPointer dummiesFacade; KisImageWSP image; bool needFinishInsertRows; bool needFinishRemoveRows; QList updateQueue; KisSignalCompressor updateTimer; KisNodeDummy* parentOfRemovedNode; QScopedPointer converter; QScopedPointer nodeInterface; QPersistentModelIndex lastClickedIndex; QVariant layerName(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); return dummy->node()->name(); } bool layerEditable(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return true; return dummy->node()->visible() && !dummy->node()->userLocked(); } bool frameExists(int row, int column) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); return (primaryChannel && primaryChannel->keyframeAt(column)); } bool specialKeyframeExists(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; QList channels = dummy->node()->keyframeChannels(); Q_FOREACH(KisKeyframeChannel *channel, channels) { if (channel->id() != KisKeyframeChannel::Content.id() && channel->keyframeAt(column)) { return true; } } return false; } int frameColorLabel(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return -1; KisKeyframeSP frame = primaryChannel->keyframeAt(column); if (!frame) return -1; return frame->colorLabel(); } void setFrameColorLabel(int row, int column, int color) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return; KisKeyframeSP frame = primaryChannel->keyframeAt(column); if (!frame) return; frame->setColorLabel(color); } int layerColorLabel(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; return dummy->node()->colorLabelIndex(); } QVariant layerProperties(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); PropertyList props = dummy->node()->sectionModelProperties(); return QVariant::fromValue(props); } bool setLayerProperties(int row, PropertyList props) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisNodePropertyListCommand::setNodePropertiesNoUndo(dummy->node(), image, props); return true; } bool addKeyframe(int row, int column, bool copy) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) return false; return KisAnimationUtils::createKeyframeLazy(image, node, KisKeyframeChannel::Content.id(), column, copy); } bool addNewLayer(int row) { Q_UNUSED(row); if (nodeInterface) { KisLayerSP layer = nodeInterface->addPaintLayer(); layer->setUseInTimeline(true); } return true; } bool removeLayer(int row) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; if (nodeInterface) { nodeInterface->removeNode(dummy->node()); } return true; } }; TimelineFramesModel::TimelineFramesModel(QObject *parent) : ModelWithExternalNotifications(parent), m_d(new Private) { connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(processUpdateQueue())); } TimelineFramesModel::~TimelineFramesModel() { } bool TimelineFramesModel::hasConnectionToCanvas() const { return m_d->dummiesFacade; } void TimelineFramesModel::setNodeManipulationInterface(NodeManipulationInterface *iface) { m_d->nodeInterface.reset(iface); } KisNodeSP TimelineFramesModel::nodeAt(QModelIndex index) const { return m_d->converter->dummyFromRow(index.row())->node(); } QList TimelineFramesModel::channelsAt(QModelIndex index) const { KisNodeDummy *srcDummy = m_d->converter->dummyFromRow(index.row()); return srcDummy->node()->keyframeChannels(); } void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image) { KisDummiesFacadeBase *oldDummiesFacade = m_d->dummiesFacade; if (m_d->dummiesFacade && m_d->image) { m_d->image->animationInterface()->disconnect(this); m_d->image->disconnect(this); m_d->dummiesFacade->disconnect(this); } m_d->image = image; KisTimeBasedItemModel::setImage(image); m_d->dummiesFacade = dummiesFacade; m_d->converter.reset(); if (m_d->dummiesFacade) { m_d->converter.reset(new TimelineNodeListKeeper(this, m_d->dummiesFacade)); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); connect(m_d->image->animationInterface(), SIGNAL(sigFullClipRangeChanged()), SIGNAL(sigInfiniteTimelineUpdateNeeded())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioChannelChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioVolumeChanged()), SIGNAL(sigAudioChannelChanged())); } if (m_d->dummiesFacade != oldDummiesFacade) { reset(); } if (m_d->dummiesFacade) { emit sigInfiniteTimelineUpdateNeeded(); emit sigAudioChannelChanged(); } } void TimelineFramesModel::slotDummyChanged(KisNodeDummy *dummy) { if (!m_d->updateQueue.contains(dummy)) { m_d->updateQueue.append(dummy); } m_d->updateTimer.start(); } void TimelineFramesModel::processUpdateQueue() { Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) { int row = m_d->converter->rowForDummy(dummy); if (row >= 0) { emit headerDataChanged (Qt::Vertical, row, row); emit dataChanged(this->index(row, 0), this->index(row, columnCount() - 1)); } } m_d->updateQueue.clear(); } void TimelineFramesModel::slotCurrentNodeChanged(KisNodeSP node) { if (!node) { m_d->activeLayerIndex = -1; return; } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node); KIS_ASSERT_RECOVER_RETURN(dummy); m_d->converter->updateActiveDummy(dummy); const int row = m_d->converter->rowForDummy(dummy); if (row < 0) { qWarning() << "WARNING: TimelineFramesModel::slotCurrentNodeChanged: node not found!"; } if (row >= 0 && m_d->activeLayerIndex != row) { setData(index(row, 0), true, ActiveLayerRole); } } int TimelineFramesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if(!m_d->dummiesFacade) return 0; return m_d->converter->rowCount(); } QVariant TimelineFramesModel::data(const QModelIndex &index, int role) const { if(!m_d->dummiesFacade) return QVariant(); switch (role) { case ActiveLayerRole: { return index.row() == m_d->activeLayerIndex; } case FrameEditableRole: { return m_d->layerEditable(index.row()); } case FrameExistsRole: { return m_d->frameExists(index.row(), index.column()); } case SpecialKeyframeExists: { return m_d->specialKeyframeExists(index.row(), index.column()); } case FrameColorLabelIndexRole: { int label = m_d->frameColorLabel(index.row(), index.column()); return label > 0 ? label : QVariant(); } case Qt::DisplayRole: { - return QVariant(); + return m_d->layerName(index.row()); } case Qt::TextAlignmentRole: { return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } + case KoResourceModel::LargeThumbnailRole: { + KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); + if (!dummy) { + return QVariant(); + } + const int maxSize = 200; + + QSize size = dummy->node()->extent().size(); + size.scale(maxSize, maxSize, Qt::KeepAspectRatio); + if (size.width() == 0 || size.height() == 0) { + // No thumbnail can be shown if there isn't width or height... + return QVariant(); + } + QImage image(dummy->node()->createThumbnailForFrame(size.width(), size.height(), index.column())); + return image; + } } return ModelWithExternalNotifications::data(index, role); } bool TimelineFramesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || !m_d->dummiesFacade) return false; switch (role) { case ActiveLayerRole: { if (value.toBool() && index.row() != m_d->activeLayerIndex) { int prevLayer = m_d->activeLayerIndex; m_d->activeLayerIndex = index.row(); emit dataChanged(this->index(prevLayer, 0), this->index(prevLayer, columnCount() - 1)); emit dataChanged(this->index(m_d->activeLayerIndex, 0), this->index(m_d->activeLayerIndex, columnCount() - 1)); emit headerDataChanged(Qt::Vertical, prevLayer, prevLayer); emit headerDataChanged(Qt::Vertical, m_d->activeLayerIndex, m_d->activeLayerIndex); KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); KIS_ASSERT_RECOVER(dummy) { return true; } emit requestCurrentNodeChanged(dummy->node()); emit sigEnsureRowVisible(m_d->activeLayerIndex); } break; } case FrameColorLabelIndexRole: { m_d->setFrameColorLabel(index.row(), index.column(), value.toInt()); } break; } return ModelWithExternalNotifications::setData(index, value, role); } QVariant TimelineFramesModel::headerData(int section, Qt::Orientation orientation, int role) const { if(!m_d->dummiesFacade) return QVariant(); if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: return section == m_d->activeLayerIndex; case Qt::DisplayRole: { QVariant value = headerData(section, orientation, Qt::ToolTipRole); if (!value.isValid()) return value; QString name = value.toString(); const int maxNameSize = 13; if (name.size() > maxNameSize) { name = QString("%1...").arg(name.left(maxNameSize)); } return name; } case Qt::TextColorRole: { // WARNING: this role doesn't work for header views! Use // bold font to show isolated mode instead! return QVariant(); } case Qt::FontRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); KisNodeSP node = dummy->node(); QFont baseFont; if (node->projectionLeaf()->isDroppedMask()) { baseFont.setStrikeOut(true); } else if (m_d->image && m_d->image->isolatedModeRoot() && KisNodeModel::belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade)) { baseFont.setBold(true); } return baseFont; } case Qt::ToolTipRole: { return m_d->layerName(section); } case TimelinePropertiesRole: { return QVariant::fromValue(m_d->layerProperties(section)); } case OtherLayersRole: { TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); return QVariant::fromValue(list); } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); return dummy->node()->useInTimeline(); } case Qt::BackgroundRole: { int label = m_d->layerColorLabel(section); if (label > 0) { KisNodeViewColorScheme scm; QColor color = scm.colorLabel(label); QPalette pal = qApp->palette(); color = KritaUtils::blendColors(color, pal.color(QPalette::Button), 0.3); return QBrush(color); } else { return QVariant(); } } } } return ModelWithExternalNotifications::headerData(section, orientation, role); } bool TimelineFramesModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (!m_d->dummiesFacade) return false; if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: { setData(index(section, 0), value, role); break; } case TimelinePropertiesRole: { TimelineFramesModel::PropertyList props = value.value(); int result = m_d->setLayerProperties(section, props); emit headerDataChanged (Qt::Vertical, section, section); return result; } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return false; dummy->node()->setUseInTimeline(value.toBool()); return true; } } } return ModelWithExternalNotifications::setHeaderData(section, orientation, value, role); } Qt::DropActions TimelineFramesModel::supportedDragActions() const { return Qt::MoveAction | Qt::CopyAction; } Qt::DropActions TimelineFramesModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } QStringList TimelineFramesModel::mimeTypes() const { QStringList types; types << QLatin1String("application/x-krita-frame"); return types; } void TimelineFramesModel::setLastClickedIndex(const QModelIndex &index) { m_d->lastClickedIndex = index; } QMimeData* TimelineFramesModel::mimeData(const QModelIndexList &indexes) const { QMimeData *data = new QMimeData(); QByteArray encoded; QDataStream stream(&encoded, QIODevice::WriteOnly); const int baseRow = m_d->lastClickedIndex.row(); const int baseColumn = m_d->lastClickedIndex.column(); stream << indexes.size(); stream << baseRow << baseColumn; Q_FOREACH (const QModelIndex &index, indexes) { stream << index.row() - baseRow << index.column() - baseColumn; } data->setData("application/x-krita-frame", encoded); return data; } inline void decodeBaseIndex(QByteArray *encoded, int *row, int *col) { int size_UNUSED = 0; QDataStream stream(encoded, QIODevice::ReadOnly); stream >> size_UNUSED >> *row >> *col; } bool TimelineFramesModel::canDropFrameData(const QMimeData */*data*/, const QModelIndex &index) { if (!index.isValid()) return false; /** * Now we support D&D around any layer, so just return 'true' all * the time. */ return true; } bool TimelineFramesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); bool result = false; if ((action != Qt::MoveAction && action != Qt::CopyAction) || !parent.isValid()) return result; const bool copyFrames = action == Qt::CopyAction; QByteArray encoded = data->data("application/x-krita-frame"); QDataStream stream(&encoded, QIODevice::ReadOnly); int size, baseRow, baseColumn; stream >> size >> baseRow >> baseColumn; QModelIndexList srcIndexes; for (int i = 0; i < size; i++) { int relRow, relColumn; stream >> relRow >> relColumn; int srcRow = baseRow + relRow; int srcColumn = baseColumn + relColumn; srcIndexes << index(srcRow, srcColumn); } const QPoint offset(parent.column() - baseColumn, parent.row() - baseRow); return offsetFrames(srcIndexes, offset, copyFrames); } Qt::ItemFlags TimelineFramesModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = ModelWithExternalNotifications::flags(index); if (!index.isValid()) return flags; if (m_d->frameExists(index.row(), index.column()) || m_d->specialKeyframeExists(index.row(), index.column())) { if (data(index, FrameEditableRole).toBool()) { flags |= Qt::ItemIsDragEnabled; } } /** * Basically we should forbid overrides only if we D&D a single frame * and allow it when we D&D multiple frames. But we cannot distinguish * it here... So allow all the time. */ flags |= Qt::ItemIsDropEnabled; return flags; } bool TimelineFramesModel::insertRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row > rowCount()) return false; bool result = m_d->addNewLayer(row); return result; } bool TimelineFramesModel::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row >= rowCount()) return false; bool result = m_d->removeLayer(row); return result; } bool TimelineFramesModel::insertOtherLayer(int index, int dstRow) { Q_UNUSED(dstRow); TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); if (index < 0 || index >= list.size()) return false; list[index].dummy->node()->setUseInTimeline(true); dstRow = m_d->converter->rowForDummy(list[index].dummy); setData(this->index(dstRow, 0), true, ActiveLayerRole); return true; } int TimelineFramesModel::activeLayerRow() const { return m_d->activeLayerIndex; } bool TimelineFramesModel::createFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; bool result = m_d->addKeyframe(dstIndex.row(), dstIndex.column(), false); if (result) { emit dataChanged(dstIndex, dstIndex); } return result; } bool TimelineFramesModel::copyFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; bool result = m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true); if (result) { emit dataChanged(dstIndex, dstIndex); } return result; } QString TimelineFramesModel::audioChannelFileName() const { return m_d->image ? m_d->image->animationInterface()->audioChannelFileName() : QString(); } void TimelineFramesModel::setAudioChannelFileName(const QString &fileName) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioChannelFileName(fileName); } bool TimelineFramesModel::isAudioMuted() const { return m_d->image ? m_d->image->animationInterface()->isAudioMuted() : false; } void TimelineFramesModel::setAudioMuted(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioMuted(value); } qreal TimelineFramesModel::audioVolume() const { return m_d->image ? m_d->image->animationInterface()->audioVolume() : 0.5; } void TimelineFramesModel::setAudioVolume(qreal value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioVolume(value); } diff --git a/plugins/dockers/animation/timeline_frames_view.cpp b/plugins/dockers/animation/timeline_frames_view.cpp index eed6437105..dfd37b3a83 100644 --- a/plugins/dockers/animation/timeline_frames_view.cpp +++ b/plugins/dockers/animation/timeline_frames_view.cpp @@ -1,1062 +1,1100 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_frames_view.h" #include "timeline_frames_model.h" #include "timeline_ruler_header.h" #include "timeline_layers_header.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "timeline_frames_item_delegate.h" #include "kis_zoom_button.h" #include "kis_icon_utils.h" #include "kis_animation_utils.h" #include "kis_custom_modifiers_catcher.h" #include "kis_action.h" #include "kis_signal_compressor.h" #include "kis_time_range.h" #include "kis_color_label_selector_widget.h" #include "kis_slider_spin_box.h" #include #include +#include #include #include #include "config-qtmultimedia.h" typedef QPair QItemViewPaintPair; typedef QList QItemViewPaintPairs; struct TimelineFramesView::Private { Private(TimelineFramesView *_q) : q(_q), fps(1), zoomStillPointIndex(-1), zoomStillPointOriginalOffset(0), dragInProgress(false), dragWasSuccessful(false), modifiersCatcher(0), selectionChangedCompressor(300, KisSignalCompressor::FIRST_INACTIVE) {} TimelineFramesView *q; TimelineFramesModel *model; TimelineRulerHeader *horizontalRuler; TimelineLayersHeader *layersHeader; int fps; int zoomStillPointIndex; int zoomStillPointOriginalOffset; QPoint initialDragPanValue; QPoint initialDragPanPos; QToolButton *addLayersButton; KisAction *showHideLayerAction; QToolButton *audioOptionsButton; KisColorLabelSelectorWidget *colorSelector; QWidgetAction *colorSelectorAction; KisColorLabelSelectorWidget *multiframeColorSelector; QWidgetAction *multiframeColorSelectorAction; QMenu *audioOptionsMenu; QAction *openAudioAction; QAction *audioMuteAction; KisSliderSpinBox *volumeSlider; QMenu *layerEditingMenu; QMenu *existingLayersMenu; QMenu *frameCreationMenu; QMenu *frameEditingMenu; QMenu *multipleFrameEditingMenu; QMap globalActions; KisZoomButton *zoomDragButton; bool dragInProgress; bool dragWasSuccessful; KisCustomModifiersCatcher *modifiersCatcher; QPoint lastPressedPosition; KisSignalCompressor selectionChangedCompressor; QStyleOptionViewItem viewOptionsV4() const; QItemViewPaintPairs draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const; QPixmap renderToPixmap(const QModelIndexList &indexes, QRect *r) const; + KoIconToolTip tip; }; TimelineFramesView::TimelineFramesView(QWidget *parent) : QTableView(parent), m_d(new Private(this)) { m_d->modifiersCatcher = new KisCustomModifiersCatcher(this); m_d->modifiersCatcher->addModifier("pan-zoom", Qt::Key_Space); m_d->modifiersCatcher->addModifier("offset-frame", Qt::Key_Alt); setCornerButtonEnabled(false); setSelectionBehavior(QAbstractItemView::SelectItems); setSelectionMode(QAbstractItemView::ExtendedSelection); setItemDelegate(new TimelineFramesItemDelegate(this)); setDragEnabled(true); setDragDropMode(QAbstractItemView::DragDrop); setAcceptDrops(true); setDropIndicatorShown(true); setDefaultDropAction(Qt::MoveAction); m_d->horizontalRuler = new TimelineRulerHeader(this); this->setHorizontalHeader(m_d->horizontalRuler); m_d->layersHeader = new TimelineLayersHeader(this); m_d->layersHeader->setSectionResizeMode(QHeaderView::Fixed); m_d->layersHeader->setDefaultSectionSize(24); m_d->layersHeader->setMinimumWidth(60); m_d->layersHeader->setHighlightSections(true); this->setVerticalHeader(m_d->layersHeader); connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), SLOT(slotUpdateInfiniteFramesCount())); connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(slotUpdateInfiniteFramesCount())); /********** New Layer Menu ***********************************************************/ m_d->addLayersButton = new QToolButton(this); m_d->addLayersButton->setAutoRaise(true); m_d->addLayersButton->setIcon(KisIconUtils::loadIcon("addlayer")); m_d->addLayersButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_d->addLayersButton->setPopupMode(QToolButton::InstantPopup); m_d->layerEditingMenu = new QMenu(this); m_d->layerEditingMenu->addAction(KisAnimationUtils::newLayerActionName, this, SLOT(slotAddNewLayer())); m_d->existingLayersMenu = m_d->layerEditingMenu->addMenu(KisAnimationUtils::addExistingLayerActionName); m_d->layerEditingMenu->addSeparator(); m_d->showHideLayerAction = new KisAction(KisAnimationUtils::showLayerActionName, this); m_d->showHideLayerAction->setActivationFlags(KisAction::ACTIVE_LAYER); connect(m_d->showHideLayerAction, SIGNAL(triggered()), SLOT(slotHideLayerFromTimeline())); m_d->showHideLayerAction->setCheckable(true); m_d->globalActions.insert("show_in_timeline", m_d->showHideLayerAction); m_d->layerEditingMenu->addAction(m_d->showHideLayerAction); m_d->layerEditingMenu->addAction(KisAnimationUtils::removeLayerActionName, this, SLOT(slotRemoveLayer())); connect(m_d->existingLayersMenu, SIGNAL(aboutToShow()), SLOT(slotUpdateLayersMenu())); connect(m_d->existingLayersMenu, SIGNAL(triggered(QAction*)), SLOT(slotAddExistingLayer(QAction*))); connect(m_d->layersHeader, SIGNAL(sigRequestContextMenu(const QPoint&)), SLOT(slotLayerContextMenuRequested(const QPoint&))); m_d->addLayersButton->setMenu(m_d->layerEditingMenu); /********** Audio Channel Menu *******************************************************/ m_d->audioOptionsButton = new QToolButton(this); m_d->audioOptionsButton->setAutoRaise(true); m_d->audioOptionsButton->setIcon(KisIconUtils::loadIcon("audio-none")); m_d->audioOptionsButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_d->audioOptionsButton->setPopupMode(QToolButton::InstantPopup); m_d->audioOptionsMenu = new QMenu(this); #ifndef HAVE_QT_MULTIMEDIA m_d->audioOptionsMenu->addSection(i18nc("@item:inmenu", "Audio playback is not supported in this build!")); #endif m_d->openAudioAction= new QAction("XXX", this); connect(m_d->openAudioAction, SIGNAL(triggered()), this, SLOT(slotSelectAudioChannelFile())); m_d->audioOptionsMenu->addAction(m_d->openAudioAction); m_d->audioMuteAction = new QAction(i18nc("@item:inmenu", "Mute"), this); m_d->audioMuteAction->setCheckable(true); connect(m_d->audioMuteAction, SIGNAL(triggered(bool)), SLOT(slotAudioChannelMute(bool))); m_d->audioOptionsMenu->addAction(m_d->audioMuteAction); m_d->audioOptionsMenu->addAction(i18nc("@item:inmenu", "Remove audio"), this, SLOT(slotAudioChannelRemove())); m_d->audioOptionsMenu->addSeparator(); m_d->volumeSlider = new KisSliderSpinBox(this); m_d->volumeSlider->setRange(0, 100); m_d->volumeSlider->setSuffix("%"); m_d->volumeSlider->setPrefix(i18nc("@item:inmenu, slider", "Volume:")); m_d->volumeSlider->setSingleStep(1); m_d->volumeSlider->setPageStep(10); m_d->volumeSlider->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); connect(m_d->volumeSlider, SIGNAL(valueChanged(int)), SLOT(slotAudioVolumeChanged(int))); QWidgetAction *volumeAction = new QWidgetAction(m_d->audioOptionsMenu); volumeAction->setDefaultWidget(m_d->volumeSlider); m_d->audioOptionsMenu->addAction(volumeAction); m_d->audioOptionsButton->setMenu(m_d->audioOptionsMenu); /********** Frame Editing Context Menu ***********************************************/ m_d->frameCreationMenu = new QMenu(this); m_d->frameCreationMenu->addAction(KisAnimationUtils::addFrameActionName, this, SLOT(slotNewFrame())); m_d->frameCreationMenu->addAction(KisAnimationUtils::duplicateFrameActionName, this, SLOT(slotCopyFrame())); m_d->colorSelector = new KisColorLabelSelectorWidget(this); m_d->colorSelectorAction = new QWidgetAction(this); m_d->colorSelectorAction->setDefaultWidget(m_d->colorSelector); connect(m_d->colorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged); m_d->frameEditingMenu = new QMenu(this); m_d->frameEditingMenu->addAction(KisAnimationUtils::removeFrameActionName, this, SLOT(slotRemoveFrame())); m_d->frameEditingMenu->addAction(m_d->colorSelectorAction); m_d->multiframeColorSelector = new KisColorLabelSelectorWidget(this); m_d->multiframeColorSelectorAction = new QWidgetAction(this); m_d->multiframeColorSelectorAction->setDefaultWidget(m_d->multiframeColorSelector); connect(m_d->multiframeColorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged); m_d->multipleFrameEditingMenu = new QMenu(this); m_d->multipleFrameEditingMenu->addAction(KisAnimationUtils::removeFramesActionName, this, SLOT(slotRemoveFrame())); m_d->multipleFrameEditingMenu->addAction(m_d->multiframeColorSelectorAction); /********** Zoom Button **************************************************************/ m_d->zoomDragButton = new KisZoomButton(this); m_d->zoomDragButton->setAutoRaise(true); m_d->zoomDragButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal")); m_d->zoomDragButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_d->zoomDragButton->setToolTip(i18nc("@info:tooltip", "Zoom Timeline. Hold down and drag left or right.")); m_d->zoomDragButton->setPopupMode(QToolButton::InstantPopup); connect(m_d->zoomDragButton, SIGNAL(zoomLevelChanged(qreal)), SLOT(slotZoomButtonChanged(qreal))); connect(m_d->zoomDragButton, SIGNAL(zoomStarted(qreal)), SLOT(slotZoomButtonPressed(qreal))); setFramesPerSecond(12); setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); connect(&m_d->selectionChangedCompressor, SIGNAL(timeout()), SLOT(slotSelectionChanged())); } TimelineFramesView::~TimelineFramesView() { } QMap TimelineFramesView::globalActions() const { return m_d->globalActions; } void resizeToMinimalSize(QAbstractButton *w, int minimalSize) { QSize buttonSize = w->sizeHint(); if (buttonSize.height() > minimalSize) { buttonSize = QSize(minimalSize, minimalSize); } w->resize(buttonSize); } void TimelineFramesView::updateGeometries() { QTableView::updateGeometries(); const int availableHeight = m_d->horizontalRuler->height(); const int margin = 2; const int minimalSize = availableHeight - 2 * margin; resizeToMinimalSize(m_d->addLayersButton, minimalSize); resizeToMinimalSize(m_d->audioOptionsButton, minimalSize); resizeToMinimalSize(m_d->zoomDragButton, minimalSize); int x = 2 * margin; int y = (availableHeight - minimalSize) / 2; m_d->addLayersButton->move(x, 2 * y); m_d->audioOptionsButton->move(x + minimalSize + 2 * margin, 2 * y); const int availableWidth = m_d->layersHeader->width(); x = availableWidth - margin - minimalSize; m_d->zoomDragButton->move(x, 2 * y); } void TimelineFramesView::setModel(QAbstractItemModel *model) { TimelineFramesModel *framesModel = qobject_cast(model); m_d->model = framesModel; QTableView::setModel(model); connect(m_d->model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)), this, SLOT(slotHeaderDataChanged(Qt::Orientation, int, int))); connect(m_d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotDataChanged(QModelIndex,QModelIndex))); connect(m_d->model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), this, SLOT(slotReselectCurrentIndex())); connect(m_d->model, SIGNAL(sigInfiniteTimelineUpdateNeeded()), this, SLOT(slotUpdateInfiniteFramesCount())); connect(m_d->model, SIGNAL(sigAudioChannelChanged()), this, SLOT(slotUpdateAudioActions())); connect(selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), &m_d->selectionChangedCompressor, SLOT(start())); connect(m_d->model, SIGNAL(sigEnsureRowVisible(int)), SLOT(slotEnsureRowVisible(int))); slotUpdateAudioActions(); } void TimelineFramesView::setFramesPerSecond(int fps) { m_d->fps = fps; m_d->horizontalRuler->setFramePerSecond(fps); // For some reason simple update sometimes doesn't work here, so // reset the whole header // // m_d->horizontalRuler->reset(); } void TimelineFramesView::slotZoomButtonPressed(qreal staticPoint) { m_d->zoomStillPointIndex = qIsNaN(staticPoint) ? currentIndex().column() : staticPoint; const int w = m_d->horizontalRuler->defaultSectionSize(); m_d->zoomStillPointOriginalOffset = w * m_d->zoomStillPointIndex - horizontalScrollBar()->value(); } void TimelineFramesView::slotZoomButtonChanged(qreal zoomLevel) { if (m_d->horizontalRuler->setZoom(zoomLevel)) { slotUpdateInfiniteFramesCount(); const int w = m_d->horizontalRuler->defaultSectionSize(); horizontalScrollBar()->setValue(w * m_d->zoomStillPointIndex - m_d->zoomStillPointOriginalOffset); viewport()->update(); } } void TimelineFramesView::slotColorLabelChanged(int label) { Q_FOREACH(QModelIndex index, selectedIndexes()) { m_d->model->setData(index, label, TimelineFramesModel::FrameColorLabelIndexRole); } KisImageConfig config; config.setDefaultFrameColorLabel(label); } void TimelineFramesView::slotSelectAudioChannelFile() { if (!m_d->model) return; QString defaultDir = QDesktopServices::storageLocation(QDesktopServices::MusicLocation); const QString currentFile = m_d->model->audioChannelFileName(); QDir baseDir = QFileInfo(currentFile).absoluteDir(); if (baseDir.exists()) { defaultDir = baseDir.absolutePath(); } const QString result = KisImportExportManager::askForAudioFileName(defaultDir, this); const QFileInfo info(result); if (info.exists()) { m_d->model->setAudioChannelFileName(info.absoluteFilePath()); } } void TimelineFramesView::slotAudioChannelMute(bool value) { if (!m_d->model) return; if (value != m_d->model->isAudioMuted()) { m_d->model->setAudioMuted(value); } } void TimelineFramesView::slotAudioChannelRemove() { if (!m_d->model) return; m_d->model->setAudioChannelFileName(QString()); } void TimelineFramesView::slotUpdateAudioActions() { if (!m_d->model) return; const QString currentFile = m_d->model->audioChannelFileName(); if (currentFile.isEmpty()) { m_d->openAudioAction->setText(i18nc("@item:inmenu", "Open audio...")); } else { QFileInfo info(currentFile); m_d->openAudioAction->setText(i18nc("@item:inmenu", "Change audio (%1)...", info.fileName())); } m_d->audioMuteAction->setChecked(m_d->model->isAudioMuted()); QIcon audioIcon; if (currentFile.isEmpty()) { audioIcon = KisIconUtils::loadIcon("audio-none"); } else { if (m_d->model->isAudioMuted()) { audioIcon = KisIconUtils::loadIcon("audio-volume-mute"); } else { audioIcon = KisIconUtils::loadIcon("audio-volume-high"); } } m_d->audioOptionsButton->setIcon(audioIcon); m_d->volumeSlider->setEnabled(!m_d->model->isAudioMuted()); KisSignalsBlocker b(m_d->volumeSlider); m_d->volumeSlider->setValue(qRound(m_d->model->audioVolume() * 100.0)); } void TimelineFramesView::slotAudioVolumeChanged(int value) { m_d->model->setAudioVolume(qreal(value) / 100.0); } void TimelineFramesView::slotUpdateInfiniteFramesCount() { if (horizontalScrollBar()->isSliderDown()) return; const int sectionWidth = m_d->horizontalRuler->defaultSectionSize(); const int calculatedIndex = (horizontalScrollBar()->value() + m_d->horizontalRuler->width() - 1) / sectionWidth; m_d->model->setLastVisibleFrame(calculatedIndex); } void TimelineFramesView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { QTableView::currentChanged(current, previous); if (previous.column() != current.column()) { m_d->model->setData(previous, false, TimelineFramesModel::ActiveFrameRole); m_d->model->setData(current, true, TimelineFramesModel::ActiveFrameRole); } } QItemSelectionModel::SelectionFlags TimelineFramesView::selectionCommand(const QModelIndex &index, const QEvent *event) const { // WARNING: Copy-pasted from KisNodeView! Please keep in sync! /** * Qt has a bug: when we Ctrl+click on an item, the item's * selections gets toggled on mouse *press*, whereas usually it is * done on mouse *release*. Therefore the user cannot do a * Ctrl+D&D with the default configuration. This code fixes the * problem by manually returning QItemSelectionModel::NoUpdate * flag when the user clicks on an item and returning * QItemSelectionModel::Toggle on release. */ if (event && (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) && index.isValid()) { const QMouseEvent *mevent = static_cast(event); if (mevent->button() == Qt::RightButton && selectionModel()->selectedIndexes().contains(index)) { // Allow calling context menu for multiple layers return QItemSelectionModel::NoUpdate; } if (event->type() == QEvent::MouseButtonPress && (mevent->modifiers() & Qt::ControlModifier)) { return QItemSelectionModel::NoUpdate; } if (event->type() == QEvent::MouseButtonRelease && (mevent->modifiers() & Qt::ControlModifier)) { return QItemSelectionModel::Toggle; } } return QAbstractItemView::selectionCommand(index, event); } void TimelineFramesView::slotSelectionChanged() { int minColumn = std::numeric_limits::max(); int maxColumn = std::numeric_limits::min(); foreach (const QModelIndex &idx, selectedIndexes()) { if (idx.column() > maxColumn) { maxColumn = idx.column(); } if (idx.column() < minColumn) { minColumn = idx.column(); } } KisTimeRange range; if (maxColumn > minColumn) { range = KisTimeRange(minColumn, maxColumn - minColumn + 1); } m_d->model->setPlaybackRange(range); } void TimelineFramesView::slotReselectCurrentIndex() { QModelIndex index = currentIndex(); currentChanged(index, index); } void TimelineFramesView::slotEnsureRowVisible(int row) { QModelIndex index = currentIndex(); if (!index.isValid() || row < 0) return; index = m_d->model->index(row, index.column()); scrollTo(index); } void TimelineFramesView::slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { if (m_d->model->isPlaybackActive()) return; int selectedColumn = -1; for (int j = topLeft.column(); j <= bottomRight.column(); j++) { QVariant value = m_d->model->data( m_d->model->index(topLeft.row(), j), TimelineFramesModel::ActiveFrameRole); if (value.isValid() && value.toBool()) { selectedColumn = j; break; } } QModelIndex index = currentIndex(); if (!index.isValid() && selectedColumn < 0) { return; } if (selectedColumn == -1) { selectedColumn = index.column(); } if (selectedColumn != index.column() && !m_d->dragInProgress) { int row= index.isValid() ? index.row() : 0; setCurrentIndex(m_d->model->index(row, selectedColumn)); } } void TimelineFramesView::slotHeaderDataChanged(Qt::Orientation orientation, int first, int last) { Q_UNUSED(first); Q_UNUSED(last); if (orientation == Qt::Horizontal) { const int newFps = m_d->model->headerData(0, Qt::Horizontal, TimelineFramesModel::FramesPerSecondRole).toInt(); if (newFps != m_d->fps) { setFramesPerSecond(newFps); } } else /* if (orientation == Qt::Vertical) */ { updateShowInTimeline(); } } void TimelineFramesView::rowsInserted(const QModelIndex& parent, int start, int end) { QTableView::rowsInserted(parent, start, end); updateShowInTimeline(); } inline bool isIndexDragEnabled(QAbstractItemModel *model, const QModelIndex &index) { return (model->flags(index) & Qt::ItemIsDragEnabled); } QStyleOptionViewItem TimelineFramesView::Private::viewOptionsV4() const { QStyleOptionViewItem option = q->viewOptions(); option.locale = q->locale(); option.locale.setNumberOptions(QLocale::OmitGroupSeparator); option.widget = q; return option; } QItemViewPaintPairs TimelineFramesView::Private::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const { Q_ASSERT(r); QRect &rect = *r; const QRect viewportRect = q->viewport()->rect(); QItemViewPaintPairs ret; for (int i = 0; i < indexes.count(); ++i) { const QModelIndex &index = indexes.at(i); const QRect current = q->visualRect(index); if (current.intersects(viewportRect)) { ret += qMakePair(current, index); rect |= current; } } rect &= viewportRect; return ret; } QPixmap TimelineFramesView::Private::renderToPixmap(const QModelIndexList &indexes, QRect *r) const { Q_ASSERT(r); QItemViewPaintPairs paintPairs = draggablePaintPairs(indexes, r); if (paintPairs.isEmpty()) return QPixmap(); QPixmap pixmap(r->size()); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); QStyleOptionViewItem option = viewOptionsV4(); option.state |= QStyle::State_Selected; for (int j = 0; j < paintPairs.count(); ++j) { option.rect = paintPairs.at(j).first.translated(-r->topLeft()); const QModelIndex ¤t = paintPairs.at(j).second; //adjustViewOptionsForIndex(&option, current); q->itemDelegate(current)->paint(&painter, option, current); } return pixmap; } void TimelineFramesView::startDrag(Qt::DropActions supportedActions) { QModelIndexList indexes = selectionModel()->selectedIndexes(); if (!indexes.isEmpty() && m_d->modifiersCatcher->modifierPressed("offset-frame")) { QVector rows; int leftmostColumn = std::numeric_limits::max(); Q_FOREACH (const QModelIndex &index, indexes) { leftmostColumn = qMin(leftmostColumn, index.column()); if (!rows.contains(index.row())) { rows.append(index.row()); } } const int lastColumn = m_d->model->columnCount() - 1; selectionModel()->clear(); Q_FOREACH (const int row, rows) { QItemSelection sel(m_d->model->index(row, leftmostColumn), m_d->model->index(row, lastColumn)); selectionModel()->select(sel, QItemSelectionModel::Select); } supportedActions = Qt::MoveAction; { QModelIndexList indexes = selectedIndexes(); for(int i = indexes.count() - 1 ; i >= 0; --i) { if (!isIndexDragEnabled(m_d->model, indexes.at(i))) indexes.removeAt(i); } selectionModel()->clear(); if (indexes.count() > 0) { QMimeData *data = m_d->model->mimeData(indexes); if (!data) return; QRect rect; QPixmap pixmap = m_d->renderToPixmap(indexes, &rect); rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); QDrag *drag = new QDrag(this); drag->setPixmap(pixmap); drag->setMimeData(data); drag->setHotSpot(m_d->lastPressedPosition - rect.topLeft()); drag->exec(supportedActions, Qt::MoveAction); setCurrentIndex(currentIndex()); } } } else { /** * Workaround for Qt5's bugs: * * 1) Qt doesn't treat selection the selection on D&D * correctly, so we save it in advance and restore * afterwards. * * 2) There is a private variable in QAbstractItemView: * QAbstractItemView::Private::currentSelectionStartIndex. * It is initialized *only* when the setCurrentIndex() is called * explicitly on the view object, not on the selection model. * Therefore we should explicitly call setCurrentIndex() after * D&D, even if it already has *correct* value! * * 2) We should also call selectionModel()->select() * explicitly. There are two reasons for it: 1) Qt doesn't * maintain selection over D&D; 2) when reselecting single * element after D&D, Qt goes crazy, because it tries to * read *global* keyboard modifiers. Therefore if we are * dragging with Shift or Ctrl pressed it'll get crazy. So * just reset it explicitly. */ QModelIndexList selectionBefore = selectionModel()->selectedIndexes(); QModelIndex currentBefore = selectionModel()->currentIndex(); // initialize a global status variable m_d->dragWasSuccessful = false; QAbstractItemView::startDrag(supportedActions); QModelIndex newCurrent; QPoint selectionOffset; if (m_d->dragWasSuccessful) { newCurrent = currentIndex(); selectionOffset = QPoint(newCurrent.column() - currentBefore.column(), newCurrent.row() - currentBefore.row()); } else { newCurrent = currentBefore; selectionOffset = QPoint(); } setCurrentIndex(newCurrent); selectionModel()->clearSelection(); Q_FOREACH (const QModelIndex &idx, selectionBefore) { QModelIndex newIndex = model()->index(idx.row() + selectionOffset.y(), idx.column() + selectionOffset.x()); selectionModel()->select(newIndex, QItemSelectionModel::Select); } } } void TimelineFramesView::dragEnterEvent(QDragEnterEvent *event) { m_d->dragInProgress = true; m_d->model->setScrubState(true); QTableView::dragEnterEvent(event); } void TimelineFramesView::dragMoveEvent(QDragMoveEvent *event) { m_d->dragInProgress = true; m_d->model->setScrubState(true); QTableView::dragMoveEvent(event); if (event->isAccepted()) { QModelIndex index = indexAt(event->pos()); if (!m_d->model->canDropFrameData(event->mimeData(), index)) { event->ignore(); } else { selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); } } } void TimelineFramesView::dropEvent(QDropEvent *event) { m_d->dragInProgress = false; m_d->model->setScrubState(false); QAbstractItemView::dropEvent(event); m_d->dragWasSuccessful = event->isAccepted(); } void TimelineFramesView::dragLeaveEvent(QDragLeaveEvent *event) { m_d->dragInProgress = false; m_d->model->setScrubState(false); QAbstractItemView::dragLeaveEvent(event); } void TimelineFramesView::mousePressEvent(QMouseEvent *event) { QPersistentModelIndex index = indexAt(event->pos()); if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { if (event->button() == Qt::RightButton) { // TODO: try calculate index under mouse cursor even when // it is outside any visible row qreal staticPoint = index.isValid() ? index.column() : currentIndex().column(); m_d->zoomDragButton->beginZoom(event->pos(), staticPoint); } else if (event->button() == Qt::LeftButton) { m_d->initialDragPanPos = event->pos(); m_d->initialDragPanValue = QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value()); } event->accept(); } else if (event->button() == Qt::RightButton) { int numSelectedItems = selectionModel()->selectedIndexes().size(); if (index.isValid() && numSelectedItems <= 1 && m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { model()->setData(index, true, TimelineFramesModel::ActiveLayerRole); model()->setData(index, true, TimelineFramesModel::ActiveFrameRole); setCurrentIndex(index); if (model()->data(index, TimelineFramesModel::FrameExistsRole).toBool() || model()->data(index, TimelineFramesModel::SpecialKeyframeExists).toBool()) { { KisSignalsBlocker b(m_d->colorSelector); QVariant colorLabel = index.data(TimelineFramesModel::FrameColorLabelIndexRole); int labelIndex = colorLabel.isValid() ? colorLabel.toInt() : 0; m_d->colorSelector->setCurrentIndex(labelIndex); } m_d->frameEditingMenu->exec(event->globalPos()); } else { m_d->frameCreationMenu->exec(event->globalPos()); } } else if (numSelectedItems > 1) { - int labelIndex = 0; + int labelIndex = -1; bool haveFrames = false; Q_FOREACH(QModelIndex index, selectedIndexes()) { haveFrames |= index.data(TimelineFramesModel::FrameExistsRole).toBool(); QVariant colorLabel = index.data(TimelineFramesModel::FrameColorLabelIndexRole); if (colorLabel.isValid()) { - if (labelIndex == 0) { + if (labelIndex == -1) { + // First label labelIndex = colorLabel.toInt(); - } else { - labelIndex = 0; + } else if (labelIndex != colorLabel.toInt()) { + // Mixed colors in selection + labelIndex = -1; break; } } } if (!haveFrames) { m_d->multiframeColorSelectorAction->setVisible(false); } else { KisSignalsBlocker b(m_d->multiframeColorSelector); m_d->multiframeColorSelector->setCurrentIndex(labelIndex); m_d->multiframeColorSelectorAction->setVisible(true); } m_d->multipleFrameEditingMenu->exec(event->globalPos()); } - + } else if (event->button() == Qt::MidButton) { + QModelIndex index = model()->buddy(indexAt(event->pos())); + if (index.isValid()) { + QStyleOptionViewItem option = viewOptions(); + option.rect = visualRect(index); + // The offset of the headers is needed to get the correct position inside the view. + m_d->tip.showTip(this, event->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index); + } + event->accept(); } else { if (index.isValid()) { m_d->model->setLastClickedIndex(index); } m_d->lastPressedPosition = QPoint(horizontalOffset(), verticalOffset()) + event->pos(); QAbstractItemView::mousePressEvent(event); } } void TimelineFramesView::mouseMoveEvent(QMouseEvent *e) { if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { if (e->buttons() & Qt::RightButton) { m_d->zoomDragButton->continueZoom(e->pos()); } else if (e->buttons() & Qt::LeftButton) { QPoint diff = e->pos() - m_d->initialDragPanPos; QPoint offset = QPoint(m_d->initialDragPanValue.x() - diff.x(), m_d->initialDragPanValue.y() - diff.y()); const int height = m_d->layersHeader->defaultSectionSize(); horizontalScrollBar()->setValue(offset.x()); verticalScrollBar()->setValue(offset.y() / height); } e->accept(); + } else if (e->buttons() == Qt::MidButton) { + QModelIndex index = model()->buddy(indexAt(e->pos())); + if (index.isValid()) { + QStyleOptionViewItem option = viewOptions(); + option.rect = visualRect(index); + // The offset of the headers is needed to get the correct position inside the view. + m_d->tip.showTip(this, e->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index); + } + e->accept(); } else { m_d->model->setScrubState(true); QTableView::mouseMoveEvent(e); } } void TimelineFramesView::mouseReleaseEvent(QMouseEvent *e) { if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { e->accept(); } else { m_d->model->setScrubState(false); QTableView::mouseReleaseEvent(e); } } void TimelineFramesView::wheelEvent(QWheelEvent *e) { QModelIndex index = currentIndex(); int column= -1; if (index.isValid()) { column= index.column() + ((e->delta() > 0) ? 1 : -1); } if (column >= 0 && !m_d->dragInProgress) { setCurrentIndex(m_d->model->index(index.row(), column)); } } void TimelineFramesView::slotUpdateLayersMenu() { QAction *action = 0; m_d->existingLayersMenu->clear(); QVariant value = model()->headerData(0, Qt::Vertical, TimelineFramesModel::OtherLayersRole); if (value.isValid()) { TimelineFramesModel::OtherLayersList list = value.value(); int i = 0; Q_FOREACH (const TimelineFramesModel::OtherLayer &l, list) { action = m_d->existingLayersMenu->addAction(l.name); action->setData(i++); } } } void TimelineFramesView::slotLayerContextMenuRequested(const QPoint &globalPos) { m_d->layerEditingMenu->exec(globalPos); } void TimelineFramesView::updateShowInTimeline() { const int row = m_d->model->activeLayerRow(); const bool status = m_d->model->headerData(row, Qt::Vertical, TimelineFramesModel::LayerUsedInTimelineRole).toBool(); m_d->showHideLayerAction->setChecked(status); } void TimelineFramesView::slotAddNewLayer() { QModelIndex index = currentIndex(); const int newRow = index.isValid() ? index.row() : 0; model()->insertRow(newRow); } void TimelineFramesView::slotAddExistingLayer(QAction *action) { QVariant value = action->data(); if (value.isValid()) { QModelIndex index = currentIndex(); const int newRow = index.isValid() ? index.row() + 1 : 0; m_d->model->insertOtherLayer(value.toInt(), newRow); } } void TimelineFramesView::slotRemoveLayer() { QModelIndex index = currentIndex(); if (!index.isValid()) return; model()->removeRow(index.row()); } void TimelineFramesView::slotHideLayerFromTimeline() { const int row = m_d->model->activeLayerRow(); const bool status = m_d->model->headerData(row, Qt::Vertical, TimelineFramesModel::LayerUsedInTimelineRole).toBool(); m_d->model->setHeaderData(row, Qt::Vertical, !status, TimelineFramesModel::LayerUsedInTimelineRole); } void TimelineFramesView::slotNewFrame() { QModelIndex index = currentIndex(); if (!index.isValid() || !m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { return; } m_d->model->createFrame(index); } void TimelineFramesView::slotCopyFrame() { QModelIndex index = currentIndex(); if (!index.isValid() || !m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { return; } m_d->model->copyFrame(index); } void TimelineFramesView::slotRemoveFrame() { QModelIndexList indexes = selectionModel()->selectedIndexes(); for (auto it = indexes.begin(); it != indexes.end(); /*noop*/) { if (!m_d->model->data(*it, TimelineFramesModel::FrameEditableRole).toBool()) { it = indexes.erase(it); } else { ++it; } } if (!indexes.isEmpty()) { m_d->model->removeFrames(indexes); } } + +bool TimelineFramesView::viewportEvent(QEvent *event) +{ + if (event->type() == QEvent::ToolTip && model()) { + QHelpEvent *he = static_cast(event); + QModelIndex index = model()->buddy(indexAt(he->pos())); + if (index.isValid()) { + QStyleOptionViewItem option = viewOptions(); + option.rect = visualRect(index); + // The offset of the headers is needed to get the correct position inside the view. + m_d->tip.showTip(this, he->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index); + return true; + } + } + + return QTableView::viewportEvent(event); +} diff --git a/plugins/dockers/animation/timeline_frames_view.h b/plugins/dockers/animation/timeline_frames_view.h index 0d05dd6172..7ada9ac895 100644 --- a/plugins/dockers/animation/timeline_frames_view.h +++ b/plugins/dockers/animation/timeline_frames_view.h @@ -1,108 +1,109 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TIMELINE_FRAMES_VIEW_H #define __TIMELINE_FRAMES_VIEW_H #include #include #include "kritaanimationdocker_export.h" class KisAction; class TimelineWidget; class KRITAANIMATIONDOCKER_EXPORT TimelineFramesView : public QTableView { Q_OBJECT public: TimelineFramesView(QWidget *parent); ~TimelineFramesView(); void setModel(QAbstractItemModel *model); void updateGeometries(); QMap globalActions() const; public Q_SLOTS: void slotSelectionChanged(); private Q_SLOTS: void slotUpdateLayersMenu(); void slotAddNewLayer(); void slotAddExistingLayer(QAction *action); void slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); void slotRemoveLayer(); void slotHideLayerFromTimeline(); void slotLayerContextMenuRequested(const QPoint &globalPos); void slotNewFrame(); void slotCopyFrame(); void slotRemoveFrame(); void slotReselectCurrentIndex(); void slotUpdateInfiniteFramesCount(); void slotHeaderDataChanged(Qt::Orientation orientation, int first, int last); void slotZoomButtonPressed(qreal staticPoint); void slotZoomButtonChanged(qreal value); void slotColorLabelChanged(int); void slotEnsureRowVisible(int row); void slotSelectAudioChannelFile(); void slotAudioChannelMute(bool value); void slotAudioChannelRemove(); void slotUpdateAudioActions(); void slotAudioVolumeChanged(int value); private: void setFramesPerSecond(int fps); void updateShowInTimeline(); protected: QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &index, const QEvent *event) const; void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); void startDrag(Qt::DropActions supportedActions); void dragEnterEvent(QDragEnterEvent *event); void dragMoveEvent(QDragMoveEvent *event); void dropEvent(QDropEvent *event); void dragLeaveEvent(QDragLeaveEvent *event); void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); void wheelEvent(QWheelEvent *e); void rowsInserted(const QModelIndex& parent, int start, int end); + bool viewportEvent(QEvent *event); private: struct Private; const QScopedPointer m_d; }; #endif /* __TIMELINE_FRAMES_VIEW_H */ diff --git a/plugins/dockers/palettedocker/palettedocker_dock.cpp b/plugins/dockers/palettedocker/palettedocker_dock.cpp index 3b6a42ec91..693be43897 100644 --- a/plugins/dockers/palettedocker/palettedocker_dock.cpp +++ b/plugins/dockers/palettedocker/palettedocker_dock.cpp @@ -1,256 +1,262 @@ /* * 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 "KisPaletteModel.h" #include "KisColorsetChooser.h" #include "ui_wdgpalettedock.h" #include "kis_palette_delegate.h" #include "kis_palette_view.h" PaletteDockerDock::PaletteDockerDock( ) : QDockWidget(i18n("Palette")) , m_wdgPaletteDock(new Ui_WdgPaletteDock()) , m_currentColorSet(0) , m_resourceProvider(0) , m_canvas(0) { QWidget* mainWidget = new QWidget(this); setWidget(mainWidget); m_wdgPaletteDock->setupUi(mainWidget); m_wdgPaletteDock->bnAdd->setIcon(KisIconUtils::loadIcon("list-add")); m_wdgPaletteDock->bnAdd->setIconSize(QSize(16, 16)); m_wdgPaletteDock->bnAddDialog->setIcon(KisIconUtils::loadIcon("document-new")); m_wdgPaletteDock->bnAddDialog->setIconSize(QSize(16, 16)); m_wdgPaletteDock->bnRemove->setIcon(KisIconUtils::loadIcon("edit-delete")); m_wdgPaletteDock->bnRemove->setIconSize(QSize(16, 16)); m_wdgPaletteDock->bnAdd->setEnabled(false); m_wdgPaletteDock->bnRemove->setEnabled(false); connect(m_wdgPaletteDock->bnAdd, SIGNAL(clicked(bool)), this, SLOT(addColorForeground())); connect(m_wdgPaletteDock->bnAddDialog, SIGNAL(clicked(bool)), this, SLOT(addColor())); connect(m_wdgPaletteDock->bnRemove, SIGNAL(clicked(bool)), this, SLOT(removeColor())); m_model = new KisPaletteModel(this); m_wdgPaletteDock->paletteView->setPaletteModel(m_model); connect(m_wdgPaletteDock->paletteView, SIGNAL(clicked(QModelIndex)), this, SLOT(entrySelected(QModelIndex))); KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(false); m_serverAdapter = QSharedPointer(new KoResourceServerAdapter(rServer)); m_serverAdapter->connectToResourceServer(); rServer->addObserver(this); m_colorSetChooser = new KisColorsetChooser(this); connect(m_colorSetChooser, SIGNAL(paletteSelected(KoColorSet*)), this, SLOT(setColorSet(KoColorSet*))); m_wdgPaletteDock->bnColorSets->setIcon(KisIconUtils::loadIcon("hi16-palette_library")); m_wdgPaletteDock->bnColorSets->setToolTip(i18n("Choose palette")); m_wdgPaletteDock->bnColorSets->setPopupWidget(m_colorSetChooser); KisConfig cfg; QString defaultPalette = cfg.defaultPalette(); KoColorSet* defaultColorSet = rServer->resourceByName(defaultPalette); if (defaultColorSet) { setColorSet(defaultColorSet); } } PaletteDockerDock::~PaletteDockerDock() { KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); rServer->removeObserver(this); if (m_currentColorSet) { KisConfig cfg; cfg.setDefaultPalette(m_currentColorSet->name()); } delete m_wdgPaletteDock->paletteView->itemDelegate(); delete m_wdgPaletteDock; } void PaletteDockerDock::setMainWindow(KisViewManager* kisview) { m_resourceProvider = kisview->resourceProvider(); connect(m_resourceProvider, SIGNAL(sigSavingWorkspace(KisWorkspaceResource*)), SLOT(saveToWorkspace(KisWorkspaceResource*))); connect(m_resourceProvider, SIGNAL(sigLoadingWorkspace(KisWorkspaceResource*)), SLOT(loadFromWorkspace(KisWorkspaceResource*))); kisview->nodeManager()->disconnect(m_model); } void PaletteDockerDock::setCanvas(KoCanvasBase *canvas) { setEnabled(canvas != 0); if (canvas) { KisCanvas2 *cv = qobject_cast(canvas); m_model->setDisplayRenderer(cv->displayColorConverter()->displayRendererInterface()); } m_canvas = static_cast(canvas); } void PaletteDockerDock::unsetCanvas() { setEnabled(false); m_model->setDisplayRenderer(0); m_canvas = 0; } void PaletteDockerDock::unsetResourceServer() { KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); rServer->removeObserver(this); } void PaletteDockerDock::removingResource(KoColorSet *resource) { if (resource == m_currentColorSet) { setColorSet(0); } } void PaletteDockerDock::resourceChanged(KoColorSet *resource) { setColorSet(resource); } void PaletteDockerDock::setColorSet(KoColorSet* colorSet) { m_model->setColorSet(colorSet); if (colorSet && colorSet->removable()) { m_wdgPaletteDock->bnAdd->setEnabled(true); m_wdgPaletteDock->bnRemove->setEnabled(false); } else { m_wdgPaletteDock->bnAdd->setEnabled(false); m_wdgPaletteDock->bnRemove->setEnabled(false); } m_currentColorSet = colorSet; } void PaletteDockerDock::addColorForeground() { if (m_resourceProvider) { KoColorSetEntry newEntry; newEntry.color = m_resourceProvider->fgColor(); m_currentColorSet->add(newEntry); m_currentColorSet->save(); setColorSet(m_currentColorSet); // update model } } void PaletteDockerDock::addColor() { if (m_currentColorSet && m_resourceProvider) { const KoColorDisplayRendererInterface *displayRenderer = m_canvas->displayColorConverter()->displayRendererInterface(); KoColor currentFgColor = m_canvas->resourceManager()->foregroundColor(); QColor color = QColorDialog::getColor(displayRenderer->toQColor(currentFgColor)); if (color.isValid()) { KoColorSetEntry newEntry; newEntry.color = displayRenderer->approximateFromRenderedQColor(color); m_currentColorSet->add(newEntry); m_currentColorSet->save(); setColorSet(m_currentColorSet); // update model } } } void PaletteDockerDock::removeColor() { QModelIndex index = m_wdgPaletteDock->paletteView->currentIndex(); if (!index.isValid()) { return; } int i = index.row()*m_model->columnCount()+index.column(); m_currentColorSet->removeAt(i); m_currentColorSet->save(); setColorSet(m_currentColorSet); // update model } void PaletteDockerDock::entrySelected(QModelIndex index) { if (!index.isValid()) { return; } - int i = index.row()*m_model->columnCount()+index.column(); + quint32 i = (quint32)(index.row()*m_model->columnCount()+index.column()); if (i < m_currentColorSet->nColors()) { - KoColorSetEntry entry = m_currentColorSet->getColor(i); - m_wdgPaletteDock->lblColorName->setText(entry.name); + KoColorSetEntry entry = m_currentColorSet->getColorGlobal(i); + quint32 li = 0; + QString groupName = m_currentColorSet->findGroupByGlobalIndex(i, &li); + if (groupName != QString()) { + groupName = groupName+" - "; + } + m_wdgPaletteDock->lblColorName->setText(groupName+entry.name); + qDebug()<<"The index of the currently selected color within its group is: "<setFGColor(entry.color); } if (m_currentColorSet->removable()) { m_wdgPaletteDock->bnRemove->setEnabled(true); } } } void PaletteDockerDock::saveToWorkspace(KisWorkspaceResource* workspace) { if (m_currentColorSet) { workspace->setProperty("palette", m_currentColorSet->name()); } } void PaletteDockerDock::loadFromWorkspace(KisWorkspaceResource* workspace) { if (workspace->hasProperty("palette")) { KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); KoColorSet* colorSet = rServer->resourceByName(workspace->getString("palette")); if (colorSet) { setColorSet(colorSet); } } } diff --git a/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDocker.cpp b/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDocker.cpp index 9f765120ec..bcaff6a69d 100644 --- a/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDocker.cpp +++ b/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDocker.cpp @@ -1,189 +1,189 @@ /* This file is part of the KDE project Copyright (C) 2007 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ShapePropertiesDocker.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class ShapePropertiesDocker::Private { public: - Private() + Private() : widgetStack(0) , currentShape(0) , currentPanel(0) - , canvas(0) + , canvas(0) { } - + QStackedWidget *widgetStack; KoShape *currentShape; KoShapeConfigWidgetBase *currentPanel; QPointer canvas; }; ShapePropertiesDocker::ShapePropertiesDocker(QWidget *parent) : QDockWidget(i18n("Shape Properties") , parent) , d(new Private()) { d->widgetStack = new QStackedWidget(); setWidget(d->widgetStack); } ShapePropertiesDocker::~ShapePropertiesDocker() { delete d; } void ShapePropertiesDocker::unsetCanvas() { setEnabled(false); d->canvas = 0; } void ShapePropertiesDocker::setCanvas(KoCanvasBase *canvas) { setEnabled(canvas != 0); if (d->canvas) { d->canvas->disconnectCanvasObserver(this); // "Every connection you make emits a signal, so duplicate connections emit two signals" } d->canvas = canvas; if (d->canvas) { connect(d->canvas->shapeManager(), SIGNAL(selectionChanged()), this, SLOT(selectionChanged())); connect(d->canvas->shapeManager(), SIGNAL(selectionContentChanged()), this, SLOT(selectionChanged())); connect(d->canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(canvasResourceChanged(int,QVariant))); } } void ShapePropertiesDocker::selectionChanged() { if (!d->canvas) { return; } KoSelection *selection = d->canvas->shapeManager()->selection(); - if (selection->count() == 1) { + if (selection && selection->count() == 1) { addWidgetForShape(selection->firstSelectedShape()); } else { addWidgetForShape(0); } } void ShapePropertiesDocker::addWidgetForShape(KoShape *shape) { // remove the config widget if a null shape is set, or the shape has changed if (!shape || shape != d->currentShape) { while (d->widgetStack->count()) { d->widgetStack->removeWidget(d->widgetStack->widget(0)); } } if (!shape) { d->currentShape = 0; d->currentPanel = 0; return; } else if (shape != d->currentShape) { // when a shape is set and is differs from the previous one // get the config widget and insert it into the option widget d->currentShape = shape; if (!d->currentShape) { return; } QString shapeId = shape->shapeId(); KoPathShape *path = dynamic_cast(shape); if (path) { // use the path specific shape id if shape is a path, otherwise use the shape id shapeId = path->pathShapeId(); // check if we have an edited parametric shape, then we use the path shape id KoParameterShape *paramShape = dynamic_cast(shape); if (paramShape && ! paramShape->isParametricShape()) { shapeId = shape->shapeId(); } } KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shapeId); if (!factory) { return; } QList panels = factory->createShapeOptionPanels(); if (!panels.count()) { return; } d->currentPanel = 0; uint panelCount = panels.count(); for (uint i = 0; i < panelCount; ++i) { if (panels[i]->showOnShapeSelect()) { d->currentPanel = panels[i]; break; } } if (d->currentPanel) { if (d->canvas) { d->currentPanel->setUnit(d->canvas->unit()); } d->widgetStack->insertWidget(0, d->currentPanel); connect(d->currentPanel, SIGNAL(propertyChanged()), this, SLOT(shapePropertyChanged())); } } if (d->currentPanel) { d->currentPanel->open(shape); } } void ShapePropertiesDocker::shapePropertyChanged() { if (d->canvas && d->currentPanel) { KUndo2Command *cmd = d->currentPanel->createCommand(); if (!cmd) { return; } d->canvas->addCommand(cmd); } } void ShapePropertiesDocker::canvasResourceChanged(int key, const QVariant &variant) { if (key == KoCanvasResourceManager::Unit && d->currentPanel) { d->currentPanel->setUnit(variant.value()); } } diff --git a/plugins/extensions/clonesarray/dlg_clonesarray.cpp b/plugins/extensions/clonesarray/dlg_clonesarray.cpp index fc5c34e789..76e3703b83 100644 --- a/plugins/extensions/clonesarray/dlg_clonesarray.cpp +++ b/plugins/extensions/clonesarray/dlg_clonesarray.cpp @@ -1,257 +1,257 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dlg_clonesarray.h" #include #include #include #include #include #include #include #include #include #include DlgClonesArray::DlgClonesArray(KisViewManager *view, QWidget *parent) : KoDialog(parent), m_view(view), m_applicator(0), m_baseLayer(m_view->activeLayer()) { Q_ASSERT(m_baseLayer); setCaption(i18n("Create Clones Array")); setButtons(Ok | Apply | Cancel); setDefaultButton(Ok); setObjectName("clones_array_dialog"); m_page = new WdgClonesArray(this); Q_CHECK_PTR(m_page); m_page->setObjectName("clones_array"); setMainWidget(m_page); resize(m_page->sizeHint()); connect(this, SIGNAL(okClicked()), SLOT(okClicked())); connect(this, SIGNAL(applyClicked()), SLOT(applyClicked())); connect(this, SIGNAL(cancelClicked()), SLOT(cancelClicked())); connect(m_page->columnXOffset, SIGNAL(valueChanged(int)), SLOT(syncOrthogonalToAngular())); connect(m_page->columnYOffset, SIGNAL(valueChanged(int)), SLOT(syncOrthogonalToAngular())); connect(m_page->rowXOffset, SIGNAL(valueChanged(int)), SLOT(syncOrthogonalToAngular())); connect(m_page->rowYOffset, SIGNAL(valueChanged(int)), SLOT(syncOrthogonalToAngular())); connect(m_page->columnDistance, SIGNAL(valueChanged(double)), SLOT(syncAngularToOrthogonal())); connect(m_page->columnAngle, SIGNAL(valueChanged(double)), SLOT(syncAngularToOrthogonal())); connect(m_page->rowDistance, SIGNAL(valueChanged(double)), SLOT(syncAngularToOrthogonal())); connect(m_page->rowAngle, SIGNAL(valueChanged(double)), SLOT(syncAngularToOrthogonal())); connect(m_page->numNegativeColumns, SIGNAL(valueChanged(int)), SLOT(setDirty())); connect(m_page->numPositiveColumns, SIGNAL(valueChanged(int)), SLOT(setDirty())); connect(m_page->numNegativeRows, SIGNAL(valueChanged(int)), SLOT(setDirty())); connect(m_page->numPositiveRows, SIGNAL(valueChanged(int)), SLOT(setDirty())); connect(m_page->numNegativeColumns, SIGNAL(valueChanged(int)), SLOT(updateCheckboxAvailability())); connect(m_page->numPositiveColumns, SIGNAL(valueChanged(int)), SLOT(updateCheckboxAvailability())); connect(m_page->numNegativeRows, SIGNAL(valueChanged(int)), SLOT(updateCheckboxAvailability())); connect(m_page->numPositiveRows, SIGNAL(valueChanged(int)), SLOT(updateCheckboxAvailability())); connect(m_page->columnPreference, SIGNAL(stateChanged(int)), SLOT(setDirty())); initializeValues(); updateCheckboxAvailability(); } DlgClonesArray::~DlgClonesArray() { delete m_page; } void DlgClonesArray::initializeValues() { if (m_baseLayer && m_baseLayer->original()) { QRect bounds = m_baseLayer->original()->exactBounds(); m_page->columnXOffset->setValue(bounds.width()); m_page->rowYOffset->setValue(bounds.height()); } } void DlgClonesArray::setDirty() { m_isDirty = true; enableButtonApply(m_isDirty); } void DlgClonesArray::setClean() { m_isDirty = false; enableButtonApply(m_isDirty); } void DlgClonesArray::updateCheckboxAvailability() { m_page->columnPreference->setEnabled( m_page->numNegativeColumns->value() > 0 || m_page->numNegativeRows->value() > 0); } void DlgClonesArray::syncOrthogonalToAngular() { setAngularSignalsEnabled(false); int x, y; x = m_page->columnXOffset->value(); y = m_page->columnYOffset->value(); m_page->columnDistance->setValue((float)sqrt(pow2(x) + pow2(y))); m_page->columnAngle->setValue(kisRadiansToDegrees(atan2((double) y, (double) x))); x = m_page->rowXOffset->value(); y = m_page->rowYOffset->value(); m_page->rowDistance->setValue((float)sqrt(pow2(x) + pow2(y))); m_page->rowAngle->setValue(kisRadiansToDegrees(atan2((double) y, (double) x))); setAngularSignalsEnabled(true); setDirty(); } void DlgClonesArray::syncAngularToOrthogonal() { setOrthogonalSignalsEnabled(false); qreal a, d; d = m_page->columnDistance->value(); a = kisDegreesToRadians(m_page->columnAngle->value()); m_page->columnXOffset->setValue(qRound(d * cos(a))); m_page->columnYOffset->setValue(qRound(d * sin(a))); d = m_page->rowDistance->value(); a = kisDegreesToRadians(m_page->rowAngle->value()); m_page->rowXOffset->setValue(qRound(d * cos(a))); m_page->rowYOffset->setValue(qRound(d * sin(a))); setOrthogonalSignalsEnabled(true); setDirty(); } void DlgClonesArray::setOrthogonalSignalsEnabled(bool value) { m_page->columnXOffset->blockSignals(!value); m_page->columnYOffset->blockSignals(!value); m_page->rowXOffset->blockSignals(!value); m_page->rowYOffset->blockSignals(!value); } void DlgClonesArray::setAngularSignalsEnabled(bool value) { m_page->columnDistance->blockSignals(!value); m_page->columnAngle->blockSignals(!value); m_page->rowDistance->blockSignals(!value); m_page->rowAngle->blockSignals(!value); } void DlgClonesArray::okClicked() { if (!m_applicator || m_isDirty) { reapplyClones(); } Q_ASSERT(m_applicator); m_applicator->end(); delete m_applicator; m_applicator = 0; } void DlgClonesArray::applyClicked() { reapplyClones(); } void DlgClonesArray::cancelClicked() { if (m_applicator) { m_applicator->cancel(); delete m_applicator; m_applicator = 0; } } void DlgClonesArray::reapplyClones() { cancelClicked(); KisImageSP image = m_view->image(); - if (!m_view->blockUntillOperationsFinished(image)) return; + if (!m_view->blockUntilOperationsFinished(image)) return; m_applicator = new KisProcessingApplicator(image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal); int columnXOffset = m_page->columnXOffset->value(); int columnYOffset = m_page->columnYOffset->value(); int rowXOffset = m_page->rowXOffset->value(); int rowYOffset = m_page->rowYOffset->value(); bool rowPreference = !m_page->columnPreference->isChecked(); int startColumn = -m_page->numNegativeColumns->value(); int startRow = -m_page->numNegativeRows->value(); int endColumn = m_page->numPositiveColumns->value() - 1; int endRow = m_page->numPositiveRows->value() - 1; QString positiveGroupName = QString(i18n("+ Array of %1")).arg(m_baseLayer->name()); KisGroupLayerSP positiveGroupLayer = new KisGroupLayer(image, positiveGroupName, OPACITY_OPAQUE_U8); m_applicator->applyCommand(new KisImageLayerAddCommand(image, positiveGroupLayer, m_baseLayer->parent(), m_baseLayer, false, true), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); KisGroupLayerSP negativeGroupLayer; if (startRow < 0 || startColumn < 0) { QString negativeGroupName = QString(i18n("- Array of %1")).arg(m_baseLayer->name()); negativeGroupLayer = new KisGroupLayer(image, negativeGroupName, OPACITY_OPAQUE_U8); m_applicator->applyCommand(new KisImageLayerAddCommand(image, negativeGroupLayer, m_baseLayer->parent(), m_baseLayer->prevSibling(), false, true), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } for (int row = endRow; row >= startRow; row--) { for (int col = endColumn; col >= startColumn; col--) { if (!col && !row) continue; bool choosePositiveGroup = rowPreference ? row > 0 || (row == 0 && col > 0) : col > 0 || (col == 0 && row > 0); KisNodeSP parent = choosePositiveGroup ? positiveGroupLayer : negativeGroupLayer; QString cloneName = QString("Clone %1, %2").arg(col).arg(row); KisCloneLayerSP clone = new KisCloneLayer(m_baseLayer, image, cloneName, OPACITY_OPAQUE_U8); clone->setX(-row * rowXOffset + col * columnXOffset); clone->setY(-row * rowYOffset + col * columnYOffset); m_applicator->applyCommand(new KisImageLayerAddCommand(image, clone, parent, 0, true, false), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } } setClean(); } diff --git a/plugins/extensions/waveletdecompose/waveletdecompose.cpp b/plugins/extensions/waveletdecompose/waveletdecompose.cpp index fc77093107..b8587afa08 100644 --- a/plugins/extensions/waveletdecompose/waveletdecompose.cpp +++ b/plugins/extensions/waveletdecompose/waveletdecompose.cpp @@ -1,161 +1,161 @@ /* * Copyright (C) 2016 Miroslav Talasek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "waveletdecompose.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dlg_waveletdecompose.h" #include "kis_node_manager.h" #include "kis_node_commands_adapter.h" #include "kis_undo_adapter.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(WaveletDecomposeFactory, "kritawaveletdecompose.json", registerPlugin();) WaveletDecompose::WaveletDecompose(QObject *parent, const QVariantList &) : KisViewPlugin(parent) { KisAction *action = createAction("waveletdecompose"); connect(action, SIGNAL(triggered()), this, SLOT(slotWaveletDecompose())); } WaveletDecompose::~WaveletDecompose() { } void WaveletDecompose::slotWaveletDecompose() { DlgWaveletDecompose dlg(m_view->mainWindow(), "WaveletDecompose"); if (dlg.exec() == QDialog::Accepted) { QApplication::setOverrideCursor(Qt::WaitCursor); KoProgressUpdater* pu = m_view->createProgressUpdater(KoProgressUpdater::Unthreaded); pu->start(100, i18n("Wavelet Decompose")); QPointer updater = pu->startSubtask(); updater->setProgress(0); KisImageSP image = m_view->image(); if (!image) return; - if (!m_view->blockUntillOperationsFinished(image)) return; + if (!m_view->blockUntilOperationsFinished(image)) return; image->barrierLock(); KisPaintDeviceSP projection = new KisPaintDevice(*(image->projection()), false, 0); if (!projection) return; const KoColorSpace *cs = projection->colorSpace(); const KoCompositeOp* op = cs->compositeOp(COMPOSITE_GRAIN_EXTRACT); int scales = dlg.scales(); QList results; const QBitArray flags(0); QRect rc = image->bounds(); KisPaintDeviceSP original = projection; //main loop for(int level = 0; level < scales; ++level){ //copy original KisPaintDeviceSP blur = new KisPaintDevice(*original, false, 0); //blur it KisWaveletKernel::applyWavelet(blur, rc, 1 << level, 1 << level, flags, 0); //do grain extract blur from original KisPainter painter(original); painter.setCompositeOp(op); painter.bitBlt(0, 0, blur, 0, 0, rc.width(), rc.height()); painter.end(); //original is new scale and blur is new original results << original; original = blur; updater->setProgress((level * 100) / scales); } //add new layers KisUndoAdapter *undo = image->undoAdapter(); undo->beginMacro(kundo2_i18n("Wavelet decompose")); KisNodeCommandsAdapter adapter(m_view); KisGroupLayerSP baseGroup = image->rootLayer(); //add layer goup KisGroupLayerSP grp = new KisGroupLayer(image, i18n("Wavelet decompose"), OPACITY_OPAQUE_U8); adapter.addNode(grp, baseGroup, baseGroup->lastChild()); baseGroup = grp; //add scales int i = 1; const KoCompositeOp* op2 = cs->compositeOp(COMPOSITE_GRAIN_MERGE); Q_FOREACH (const KisPaintDeviceSP &l, results) { KisPaintLayerSP paintLayer = new KisPaintLayer(image, QStringLiteral("Scale %1").arg(i), OPACITY_OPAQUE_U8, l); adapter.addNode(paintLayer, baseGroup, 0); adapter.setCompositeOp(paintLayer, op2); ++i; } //add residual KisPaintLayerSP paintLayer = new KisPaintLayer(image, "Residual", OPACITY_OPAQUE_U8, original); adapter.addNode(paintLayer, baseGroup, 0); undo->endMacro(); updater->setProgress(100); image->unlock(); image->setModified(); } QApplication::restoreOverrideCursor(); } #include "waveletdecompose.moc" diff --git a/plugins/filters/gradientmap/krita_filter_gradient_map.cpp b/plugins/filters/gradientmap/krita_filter_gradient_map.cpp index 44fbc58b5a..4d9a1a08ea 100644 --- a/plugins/filters/gradientmap/krita_filter_gradient_map.cpp +++ b/plugins/filters/gradientmap/krita_filter_gradient_map.cpp @@ -1,103 +1,103 @@ /* * This file is part of the KDE project * * Copyright (c) 2016 Spencer Brown * 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 "krita_filter_gradient_map.h" #include #include #include #include #include #include "kis_config_widget.h" #include #include #include #include #include "gradientmap.h" #include KritaFilterGradientMap::KritaFilterGradientMap() : KisFilter(id(), categoryMap(), i18n("&Gradient Map...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setShowConfigurationWidget(true); setSupportsLevelOfDetail(true); setSupportsPainting(true); setSupportsAdjustmentLayers(false); setSupportsThreading(true); } void KritaFilterGradientMap::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const { Q_ASSERT(!device.isNull()); if (progressUpdater) { progressUpdater->setRange(0, applyRect.height() * applyRect.width()); } KoAbstractGradient *gradient = KoResourceServerProvider::instance()->gradientServer(false)->resourceByName(config->getString("gradientName")); if (!gradient) { qDebug() << "Could not find gradient" << config->getString("gradientName"); return; } KoColorSet *gradientCache = new KoColorSet(); for (int i=0; i<256; i++) { KoColor gc; gradient->colorAt(gc, ((qreal)i/255.0)); KoColorSetEntry col; col.color = gc; gradientCache->add(col); } KoColor outColor(Qt::white, device->colorSpace()); KisSequentialIterator it(device, applyRect); int p = 0; quint8 grey; const int pixelSize = device->colorSpace()->pixelSize(); do { grey = device->colorSpace()->intensity8(it.oldRawData()); - outColor = gradientCache->getColor((quint32)grey).color; + outColor = gradientCache->getColorGlobal((quint32)grey).color; outColor.setOpacity(qMin(KoColor(it.oldRawData(), device->colorSpace()).opacityF(), outColor.opacityF())); outColor.convertTo(device->colorSpace()); memcpy(it.rawData(), outColor.data(), pixelSize); if (progressUpdater) progressUpdater->setValue(p++); } while (it.nextPixel()); } KisFilterConfigurationSP KritaFilterGradientMap::factoryConfiguration() const { KisFilterConfigurationSP config = new KisFilterConfiguration("gradientmap", 1); KoAbstractGradient *gradient = KoResourceServerProvider::instance()->gradientServer(false)->resources().first(); config->setProperty("gradientName", gradient->name()); return config; } KisConfigWidget * KritaFilterGradientMap::createConfigurationWidget(QWidget * parent, const KisPaintDeviceSP dev) const { return new KritaGradientMapConfigWidget(parent, dev); } diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp index fdd784fa8e..20dd1a6b9f 100644 --- a/plugins/impex/kra/kra_converter.cpp +++ b/plugins/impex/kra/kra_converter.cpp @@ -1,352 +1,353 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kra_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char CURRENT_DTD_VERSION[] = "2.0"; KraConverter::KraConverter(KisDocument *doc) : m_doc(doc) , m_image(doc->savingImage()) { } KraConverter::~KraConverter() { delete m_store; delete m_kraSaver; delete m_kraLoader; } KisImageBuilder_Result KraConverter::buildImage(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Read, "", KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Not a valid Krita file")); return KisImageBuilder_RESULT_FAILURE; } bool success; { if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml) KoXmlDocument doc = KoXmlDocument(true); bool ok = oldLoadAndParse(m_store, "root", doc); if (ok) ok = loadXML(doc, m_store); if (!ok) { return KisImageBuilder_RESULT_FAILURE; } } else { errUI << "ERROR: No maindoc.xml" << endl; m_doc->setErrorMessage(i18n("Invalid document: no file 'maindoc.xml'.")); return KisImageBuilder_RESULT_FAILURE; } if (m_store->hasFile("documentinfo.xml")) { KoXmlDocument doc = KoXmlDocument(true); if (oldLoadAndParse(m_store, "documentinfo.xml", doc)) { m_doc->documentInfo()->load(doc); } } success = completeLoading(m_store); } return success ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE; } KisImageSP KraConverter::image() { return m_image; } vKisNodeSP KraConverter::activeNodes() { return m_activeNodes; } QList KraConverter::assistants() { return m_assistants; } KisImageBuilder_Result KraConverter::buildFile(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Write, m_doc->nativeFormatMimeType(), KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Could not create the file for saving")); return KisImageBuilder_RESULT_FAILURE; } bool result = false; m_kraSaver = new KisKraSaver(m_doc); result = saveRootDocuments(m_store); if (!result) { return KisImageBuilder_RESULT_FAILURE; } result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true); if (!result) { qWarning() << "saving key frames failed"; } result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving()); if (!result) { qWarning() << "saving binary data failed"; } if (!m_store->finalize()) { return KisImageBuilder_RESULT_FAILURE; } if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); return KisImageBuilder_RESULT_FAILURE; } return KisImageBuilder_RESULT_OK; } bool KraConverter::saveRootDocuments(KoStore *store) { dbgFile << "Saving root"; if (store->open("root")) { KoStoreDevice dev(store); if (!saveToStream(&dev) || !store->close()) { dbgUI << "saveToStream failed"; return false; } } else { m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"))); return false; } bool success = false; if (store->open("documentinfo.xml")) { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = m_doc->documentInfo()->save(doc); KoStoreDevice dev(store); QByteArray s = doc.toByteArray(); // this is already Utf8! success = dev.write(s.data(), s.size()); store->close(); } if (store->open("preview.png")) { // ### TODO: missing error checking (The partition could be full!) savePreview(store); (void)store->close(); } dbgUI << "Saving done of url:" << m_doc->url().toLocalFile(); // Success return success; } bool KraConverter::saveToStream(QIODevice *dev) { QDomDocument doc = createDomDocument(); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open(QIODevice::WriteOnly); int nwritten = dev->write(s.data(), s.size()); if (nwritten != (int)s.size()) { warnUI << "wrote " << nwritten << "- expected" << s.size(); } return nwritten == (int)s.size(); } QDomDocument KraConverter::createDomDocument() { QDomDocument doc = m_doc->createDomDocument("DOC", CURRENT_DTD_VERSION); QDomElement root = doc.documentElement(); root.setAttribute("editor", "Krita"); root.setAttribute("syntaxVersion", "2"); root.setAttribute("kritaVersion", KritaVersionWrapper::versionString(false)); root.appendChild(m_kraSaver->saveXML(doc, m_image)); if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); } return doc; } bool KraConverter::savePreview(KoStore *store) { QPixmap pix = m_doc->generatePreview(QSize(256, 256)); QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); if (preview.size() == QSize(0,0)) { QSize newSize = m_doc->savingImage()->bounds().size(); newSize.scale(QSize(256, 256), Qt::KeepAspectRatio); preview = QImage(newSize, QImage::Format_ARGB32); preview.fill(QColor(0, 0, 0, 0)); } KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { return false; } bool ret = preview.save(&io, "PNG"); io.close(); return ret; } bool KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc) { //dbgUI <<"Trying to open" << filename; if (!store->open(filename)) { warnUI << "Entry " << filename << " not found!"; m_doc->setErrorMessage(i18n("Could not find %1", filename)); return false; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = xmldoc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); store->close(); if (!ok) { errUI << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; m_doc->setErrorMessage(i18n("Parsing error in %1 at line %2, column %3\nError message: %4" , filename , errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0, QCoreApplication::UnicodeUTF8))); return false; } dbgUI << "File" << filename << " loaded and parsed"; return true; } bool KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store) { Q_UNUSED(store); KoXmlElement root; KoXmlNode node; if (doc.doctype().name() != "DOC") { m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted")); return false; } root = doc.documentElement(); int syntaxVersion = root.attribute("syntaxVersion", "3").toInt(); if (syntaxVersion > 2) { m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion)); return false; } if (!root.hasChildNodes()) { m_doc->setErrorMessage(i18n("The file has no layers.")); return false; } m_kraLoader = new KisKraLoader(m_doc, syntaxVersion); // Legacy from the multi-image .kra file period. for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isElement()) { if (node.nodeName() == "IMAGE") { KoXmlElement elem = node.toElement(); if (!(m_image = m_kraLoader->loadXML(elem))) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { - m_doc->setErrorMessage(m_kraLoader->errorMessages().join(".\n")); + m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } return true; } else { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("The file does not contain an image.")); } return false; } } } return false; } bool KraConverter::completeLoading(KoStore* store) { if (!m_image) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { - m_doc->setErrorMessage(m_kraLoader->errorMessages().join(".\n")); + m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } m_image->blockUpdates(); m_kraLoader->loadBinaryData(store, m_image, m_doc->localFilePath(), true); m_image->unblockUpdates(); bool retval = true; - if (!m_kraLoader->errorMessages().isEmpty()) { - m_doc->setErrorMessage(m_kraLoader->errorMessages().join(".\n")); - retval = false; + + if (!m_kraLoader->warningMessages().isEmpty()) { + m_doc->setWarningMessage(m_kraLoader->warningMessages().join("\n")); + retval = true; } if (retval) { m_activeNodes = m_kraLoader->selectedNodes(); m_assistants = m_kraLoader->assistants(); } return retval; } void KraConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/libkra/kis_kra_load_visitor.cpp b/plugins/impex/libkra/kis_kra_load_visitor.cpp index 132dba8e9f..89acc985a5 100644 --- a/plugins/impex/libkra/kis_kra_load_visitor.cpp +++ b/plugins/impex/libkra/kis_kra_load_visitor.cpp @@ -1,656 +1,661 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_load_visitor.h" #include "kis_kra_tags.h" #include "flake/kis_shape_layer.h" #include #include #include #include #include #include #include // kritaimage #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_transform_mask_params_factory_registry.h" #include #include #include #include #include "kis_shape_selection.h" #include "kis_colorize_dom_utils.h" #include "kis_dom_utils.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_frames_interface.h" using namespace KRA; QString expandEncodedDirectory(const QString& _intern) { QString intern = _intern; QString result; int pos; while ((pos = intern.indexOf('/')) != -1) { if (QChar(intern.at(0)).isDigit()) result += "part"; result += intern.left(pos + 1); // copy numbers (or "pictures") + "/" intern = intern.mid(pos + 1); // remove the dir we just processed } if (!intern.isEmpty() && QChar(intern.at(0)).isDigit()) result += "part"; result += intern; return result; } KisKraLoadVisitor::KisKraLoadVisitor(KisImageSP image, KoStore *store, QMap &layerFilenames, QMap &keyframeFilenames, const QString & name, int syntaxVersion) : KisNodeVisitor(), m_layerFilenames(layerFilenames), m_keyframeFilenames(keyframeFilenames) { m_external = false; m_image = image; m_store = store; m_name = name; m_store->pushDirectory(); if (m_name.startsWith("/")) { m_name.remove(0, 1); } if (!m_store->enterDirectory(m_name)) { QStringList directories = m_store->directoryList(); dbgKrita << directories; if (directories.size() > 0) { dbgFile << "Could not locate the directory, maybe some encoding issue? Grab the first directory, that'll be the image one." << m_name << directories; m_name = directories.first(); } else { dbgFile << "Could not enter directory" << m_name << ", probably an old-style file with 'part' added."; m_name = expandEncodedDirectory(m_name); } } else { m_store->popDirectory(); } m_syntaxVersion = syntaxVersion; } void KisKraLoadVisitor::setExternalUri(const QString &uri) { m_external = true; m_uri = uri; } bool KisKraLoadVisitor::visit(KisExternalLayer * layer) { bool result = false; if (KisShapeLayer* shapeLayer = dynamic_cast(layer)) { if (!loadMetaData(layer)) { return false; } m_store->pushDirectory(); m_store->enterDirectory(getLocation(layer, DOT_SHAPE_LAYER)) ; result = shapeLayer->loadLayer(m_store); m_store->popDirectory(); } result = visitAll(layer) && result; return result; } bool KisKraLoadVisitor::visit(KisPaintLayer *layer) { loadNodeKeyframes(layer); dbgFile << "Visit: " << layer->name() << " colorSpace: " << layer->colorSpace()->id(); if (!loadPaintDevice(layer->paintDevice(), getLocation(layer))) { return false; } if (!loadProfile(layer->paintDevice(), getLocation(layer, DOT_ICC))) { return false; } if (!loadMetaData(layer)) { return false; } if (m_syntaxVersion == 1) { // Check whether there is a file with a .mask extension in the // layer directory, if so, it's an old-style transparency mask // that should be converted. QString location = getLocation(layer, ".mask"); if (m_store->open(location)) { KisSelectionSP selection = KisSelectionSP(new KisSelection()); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); if (!pixelSelection->read(m_store->device())) { pixelSelection->disconnect(); } else { KisTransparencyMask* mask = new KisTransparencyMask(); mask->setSelection(selection); m_image->addNode(mask, layer, layer->firstChild()); } m_store->close(); } } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGroupLayer *layer) { if (*layer->colorSpace() != *m_image->colorSpace()) { layer->resetCache(m_image->colorSpace()); } if (!loadMetaData(layer)) { return false; } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisAdjustmentLayer* layer) { loadNodeKeyframes(layer); // Adjustmentlayers are tricky: there's the 1.x style and the 2.x // style, which has selections with selection components bool result = true; if (m_syntaxVersion == 1) { KisSelectionSP selection = new KisSelection(); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); result = loadPaintDevice(pixelSelection, getLocation(layer, ".selection")); layer->setInternalSelection(selection); } else if (m_syntaxVersion == 2) { result = loadSelection(getLocation(layer), layer->internalSelection()); } else { // We use the default, empty selection } if (!loadMetaData(layer)) { return false; } loadFilterConfiguration(layer->filter().data(), getLocation(layer, DOT_FILTERCONFIG)); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGeneratorLayer* layer) { if (!loadMetaData(layer)) { return false; } bool result = true; loadNodeKeyframes(layer); result = loadSelection(getLocation(layer), layer->internalSelection()); result = loadFilterConfiguration(layer->filter().data(), getLocation(layer, DOT_FILTERCONFIG)); layer->update(); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisCloneLayer *layer) { if (!loadMetaData(layer)) { return false; } // the layer might have already been lazily initialized // from the mask loading code if (layer->copyFrom()) { return true; } KisNodeSP srcNode = layer->copyFromInfo().findNode(m_image->rootLayer()); KisLayerSP srcLayer = qobject_cast(srcNode.data()); Q_ASSERT(srcLayer); layer->setCopyFrom(srcLayer); // Clone layers have no data except for their masks bool result = visitAll(layer); return result; } void KisKraLoadVisitor::initSelectionForMask(KisMask *mask) { KisLayer *cloneLayer = dynamic_cast(mask->parent().data()); if (cloneLayer) { // the clone layers should be initialized out of order // and lazily, because their original() is still not // initialized cloneLayer->accept(*this); } KisLayer *parentLayer = qobject_cast(mask->parent().data()); // the KisKraLoader must have already set the parent for us Q_ASSERT(parentLayer); mask->initSelection(parentLayer); } bool KisKraLoadVisitor::visit(KisFilterMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); bool result = true; result = loadSelection(getLocation(mask), mask->selection()); result = loadFilterConfiguration(mask->filter().data(), getLocation(mask, DOT_FILTERCONFIG)); return result; } bool KisKraLoadVisitor::visit(KisTransformMask *mask) { QString location = getLocation(mask, DOT_TRANSFORMCONFIG); if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement rootElement = doc.documentElement(); QDomElement main; if (!KisDomUtils::findOnlyElement(rootElement, "main", &main/*, &m_errorMessages*/)) { return false; } QString id = main.attribute("id", "not-valid"); if (id == "not-valid") { m_errorMessages << i18n("Could not load \"id\" of the transform mask"); return false; } QDomElement data; if (!KisDomUtils::findOnlyElement(rootElement, "data", &data, &m_errorMessages)) { return false; } KisTransformMaskParamsInterfaceSP params = KisTransformMaskParamsFactoryRegistry::instance()->createParams(id, data); if (!params) { m_errorMessages << i18n("Could not create transform mask params"); return false; } mask->setTransformParams(params); loadNodeKeyframes(mask); params->clearChangedFlag(); return true; } } return false; } bool KisKraLoadVisitor::visit(KisTransparencyMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisSelectionMask *mask) { initSelectionForMask(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisColorizeMask *mask) { m_store->pushDirectory(); QString location = getLocation(mask, DOT_COLORIZE_MASK); m_store->enterDirectory(location) ; QByteArray data; if (!m_store->extractFile("content.xml", data)) return false; QDomDocument doc; if (!doc.setContent(data)) return false; QVector strokes; if (!KisDomUtils::loadValue(doc.documentElement(), COLORIZE_KEYSTROKES_SECTION, &strokes, mask->colorSpace())) return false; int i = 0; Q_FOREACH (const KisLazyFillTools::KeyStroke &stroke, strokes) { const QString fileName = QString("%1_%2").arg(COLORIZE_KEYSTROKE).arg(i++); loadPaintDevice(stroke.dev, fileName); } mask->setKeyStrokesDirect(QList::fromVector(strokes)); loadPaintDevice(mask->coloringProjection(), COLORIZE_COLORING_DEVICE); m_store->popDirectory(); return true; } QStringList KisKraLoadVisitor::errorMessages() const { return m_errorMessages; } +QStringList KisKraLoadVisitor::warningMessages() const +{ + return m_warningMessages; +} + struct SimpleDevicePolicy { bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->read(stream); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->setDefaultPixel(defaultPixel); } }; struct FramedDevicePolicy { FramedDevicePolicy(int frameId) : m_frameId(frameId) {} bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->framesInterface()->readFrame(stream, m_frameId); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->framesInterface()->setFrameDefaultPixel(defaultPixel, m_frameId); } int m_frameId; }; bool KisKraLoadVisitor::loadPaintDevice(KisPaintDeviceSP device, const QString& location) { // Layer data - KisPaintDeviceFramesInterface *frameInterface = device->framesInterface(); QList frames; if (frameInterface) { frames = device->framesInterface()->frames(); } if (!frameInterface || frames.count() <= 1) { return loadPaintDeviceFrame(device, location, SimpleDevicePolicy()); } else { KisRasterKeyframeChannel *keyframeChannel = device->keyframeChannel(); for (int i = 0; i < frames.count(); i++) { int id = frames[i]; - QString frameFilename = getLocation(keyframeChannel->frameFilename(id)); - Q_ASSERT(!frameFilename.isEmpty()); - - if (!loadPaintDeviceFrame(device, frameFilename, FramedDevicePolicy(id))) { - return false; + if (keyframeChannel->frameFilename(id).isEmpty()) { + m_warningMessages << i18n("Could not find keyframe pixel data for frame %1 in %2.").arg(id).arg(location); + } + else { + Q_ASSERT(!keyframeChannel->frameFilename(id).isEmpty()); + QString frameFilename = getLocation(keyframeChannel->frameFilename(id)); + Q_ASSERT(!frameFilename.isEmpty()); + + if (!loadPaintDeviceFrame(device, frameFilename, FramedDevicePolicy(id))) { + m_warningMessages << i18n("Could not load keyframe pixel data for frame %1 in %2.").arg(id).arg(location); + } } } } return true; } template bool KisKraLoadVisitor::loadPaintDeviceFrame(KisPaintDeviceSP device, const QString &location, DevicePolicy policy) { if (m_store->open(location)) { if (!policy.read(device, m_store->device())) { - m_errorMessages << i18n("Could not read pixel data: %1.", location); + m_warningMessages << i18n("Could not read pixel data: %1.", location); device->disconnect(); m_store->close(); - return false; + return true; } m_store->close(); } else { - m_errorMessages << i18n("Could not load pixel data: %1.", location); - return false; + m_warningMessages << i18n("Could not load pixel data: %1.", location); + return true; } if (m_store->open(location + ".defaultpixel")) { int pixelSize = device->colorSpace()->pixelSize(); if (m_store->size() == pixelSize) { KoColor color(Qt::transparent, device->colorSpace()); m_store->read((char*)color.data(), pixelSize); policy.setDefaultPixel(device, color); } m_store->close(); } return true; } bool KisKraLoadVisitor::loadProfile(KisPaintDeviceSP device, const QString& location) { if (m_store->hasFile(location)) { m_store->open(location); QByteArray data; data.resize(m_store->size()); dbgFile << "Data to load: " << m_store->size() << " from " << location << " with color space " << device->colorSpace()->id(); int read = m_store->read(data.data(), m_store->size()); dbgFile << "Profile size: " << data.size() << " " << m_store->atEnd() << " " << m_store->device()->bytesAvailable() << " " << read; m_store->close(); // Create a colorspace with the embedded profile const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(device->colorSpace()->colorModelId().id(), device->colorSpace()->colorDepthId().id(), data); if (device->setProfile(profile)) { return true; } } - m_errorMessages << i18n("Could not load profile %1.", location); - return false; + m_warningMessages << i18n("Could not load profile: %1.", location); + return true; } bool KisKraLoadVisitor::loadFilterConfiguration(KisFilterConfigurationSP kfc, const QString& location) { if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { - QString xml(data); QDomDocument doc; doc.setContent(data); QDomElement e = doc.documentElement(); if (e.tagName() == "filterconfig") { kfc->fromLegacyXML(e); } else { kfc->fromXML(e); } return true; } } - m_errorMessages << i18n("Could not filter configuration %1.", location); - return false; + m_warningMessages << i18n("Could not filter configuration %1.", location); + return true; } bool KisKraLoadVisitor::loadMetaData(KisNode* node) { dbgFile << "Load metadata for " << node->name(); KisLayer* layer = qobject_cast(node); if (!layer) return true; - bool result = true; - KisMetaData::IOBackend* backend = KisMetaData::IOBackendRegistry::instance()->get("xmp"); if (!backend || !backend->supportLoading()) { if (backend) dbgFile << "Backend " << backend->id() << " does not support loading."; else dbgFile << "Could not load the XMP backenda t all"; return true; } QString location = getLocation(node, QString(".") + backend->id() + DOT_METADATA); dbgFile << "going to load " << backend->id() << ", " << backend->name() << " from " << location; if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); QBuffer buffer(&data); if (!backend->loadFrom(layer->metaData(), &buffer)) { - m_errorMessages << i18n("Could not load metadata for layer %1.", layer->name()); - result = false; + m_warningMessages << i18n("Could not load metadata for layer %1.", layer->name()); } - } - return result; + return true; } bool KisKraLoadVisitor::loadSelection(const QString& location, KisSelectionSP dstSelection) { // Pixel selection bool result = true; QString pixelSelectionLocation = location + DOT_PIXEL_SELECTION; if (m_store->hasFile(pixelSelectionLocation)) { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); result = loadPaintDevice(pixelSelection, pixelSelectionLocation); if (!result) { - m_errorMessages << i18n("Could not load raster selection %1.", location); + m_warningMessages << i18n("Could not load raster selection %1.", location); } pixelSelection->invalidateOutlineCache(); } // Shape selection QString shapeSelectionLocation = location + DOT_SHAPE_SELECTION; if (m_store->hasFile(shapeSelectionLocation + "/content.xml")) { m_store->pushDirectory(); m_store->enterDirectory(shapeSelectionLocation) ; KisShapeSelection* shapeSelection = new KisShapeSelection(m_image, dstSelection); dstSelection->setShapeSelection(shapeSelection); result = shapeSelection->loadSelection(m_store); m_store->popDirectory(); if (!result) { - m_errorMessages << i18n("Could not load vector selection %1.", location); + m_warningMessages << i18n("Could not load vector selection %1.", location); } } - return result; + return true; } QString KisKraLoadVisitor::getLocation(KisNode* node, const QString& suffix) { return getLocation(m_layerFilenames[node], suffix); } QString KisKraLoadVisitor::getLocation(const QString &filename, const QString& suffix) { QString location = m_external ? QString() : m_uri; location += m_name + LAYER_PATH + filename + suffix; return location; } void KisKraLoadVisitor::loadNodeKeyframes(KisNode *node) { if (!m_keyframeFilenames.contains(node)) return; node->enableAnimation(); const QString &location = getLocation(m_keyframeFilenames[node]); if (!m_store->open(location)) { m_errorMessages << i18n("Could not load keyframes from %1.", location); return; } QString errorMsg; int errorLine; int errorColumn; KoXmlDocument doc = KoXmlDocument(true); bool ok = doc.setContent(m_store->device(), &errorMsg, &errorLine, &errorColumn); m_store->close(); if (!ok) { m_errorMessages << i18n("parsing error in the keyframe file %1 at line %2, column %3\nError message: %4", location, errorLine, errorColumn, i18n(errorMsg.toUtf8())); return; } QDomDocument dom; KoXml::asQDomElement(dom, doc.documentElement()); QDomElement root = dom.firstChildElement(); for (QDomElement child = root.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (child.nodeName().toUpper() == "CHANNEL") { QString id = child.attribute("name"); KisKeyframeChannel *channel = node->getKeyframeChannel(id, true); if (!channel) { - m_errorMessages << i18n("unknown keyframe channel type: %1 in %2", id, location); + m_warningMessages << i18n("unknown keyframe channel type: %1 in %2", id, location); continue; } channel->loadXML(child); } } } diff --git a/plugins/impex/libkra/kis_kra_load_visitor.h b/plugins/impex/libkra/kis_kra_load_visitor.h index 9f08fc046d..6d7392c3f3 100644 --- a/plugins/impex/libkra/kis_kra_load_visitor.h +++ b/plugins/impex/libkra/kis_kra_load_visitor.h @@ -1,96 +1,98 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_KRA_LOAD_VISITOR_H_ #define KIS_KRA_LOAD_VISITOR_H_ #include #include // kritaimage #include "kis_types.h" #include "kis_node_visitor.h" #include "kritalibkra_export.h" class KisFilterConfiguration; class KoStore; class KRITALIBKRA_EXPORT KisKraLoadVisitor : public KisNodeVisitor { public: KisKraLoadVisitor(KisImageSP image, KoStore *store, QMap &layerFilenames, QMap &keyframeFilenames, const QString & name, int syntaxVersion); public: void setExternalUri(const QString &uri); bool visit(KisNode*) { return true; } bool visit(KisExternalLayer *); bool visit(KisPaintLayer *layer); bool visit(KisGroupLayer *layer); bool visit(KisAdjustmentLayer* layer); bool visit(KisGeneratorLayer* layer); bool visit(KisCloneLayer *layer); bool visit(KisFilterMask *mask); bool visit(KisTransformMask *mask); bool visit(KisTransparencyMask *mask); bool visit(KisSelectionMask *mask); bool visit(KisColorizeMask *mask); QStringList errorMessages() const; + QStringList warningMessages() const; private: bool loadPaintDevice(KisPaintDeviceSP device, const QString& location); template bool loadPaintDeviceFrame(KisPaintDeviceSP device, const QString &location, DevicePolicy policy); bool loadProfile(KisPaintDeviceSP device, const QString& location); bool loadFilterConfiguration(KisFilterConfigurationSP kfc, const QString& location); bool loadMetaData(KisNode* node); void initSelectionForMask(KisMask *mask); bool loadSelection(const QString& location, KisSelectionSP dstSelection); QString getLocation(KisNode* node, const QString& suffix = QString()); QString getLocation(const QString &filename, const QString &suffix = QString()); void loadNodeKeyframes(KisNode *node); private: KisImageSP m_image; KoStore *m_store; bool m_external; QString m_uri; QMap m_layerFilenames; QMap m_keyframeFilenames; QString m_name; int m_syntaxVersion; QStringList m_errorMessages; + QStringList m_warningMessages; }; #endif // KIS_KRA_LOAD_VISITOR_H_ diff --git a/plugins/impex/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp index fd5e6ce844..d9fac5a1e8 100644 --- a/plugins/impex/libkra/kis_kra_loader.cpp +++ b/plugins/impex/libkra/kis_kra_loader.cpp @@ -1,1156 +1,1153 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2007 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_kra_loader.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 "lazybrush/kis_colorize_mask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_resource_server_provider.h" #include "kis_keyframe_channel.h" #include #include "KisDocument.h" #include "kis_config.h" #include "kis_kra_tags.h" #include "kis_kra_utils.h" #include "kis_kra_load_visitor.h" #include "kis_dom_utils.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_config.h" #include "KisProofingConfiguration.h" #include "kis_layer_properties_icons.h" #include "kis_node_view_color_scheme.h" /* Color model id comparison through the ages: 2.4 2.5 2.6 ideal ALPHA ALPHA ALPHA ALPHAU8 CMYK CMYK CMYK CMYKAU8 CMYKAF32 CMYKAF32 CMYKA16 CMYKAU16 CMYKAU16 GRAYA GRAYA GRAYA GRAYAU8 GrayF32 GRAYAF32 GRAYAF32 GRAYA16 GRAYAU16 GRAYAU16 LABA LABA LABA LABAU16 LABAF32 LABAF32 LABAU8 LABAU8 RGBA RGBA RGBA RGBAU8 RGBA16 RGBA16 RGBA16 RGBAU16 RgbAF32 RGBAF32 RGBAF32 RgbAF16 RgbAF16 RGBAF16 XYZA16 XYZA16 XYZA16 XYZAU16 XYZA8 XYZA8 XYZAU8 XyzAF16 XyzAF16 XYZAF16 XyzAF32 XYZAF32 XYZAF32 YCbCrA YCBCRA8 YCBCRA8 YCBCRAU8 YCbCrAU16 YCBCRAU16 YCBCRAU16 YCBCRF32 YCBCRF32 */ using namespace KRA; struct KisKraLoader::Private { public: KisDocument* document; QString imageName; // used to be stored in the image, is now in the documentInfo block QString imageComment; // used to be stored in the image, is now in the documentInfo block QMap layerFilenames; // temp storage during loading int syntaxVersion; // version of the fileformat we are loading vKisNodeSP selectedNodes; // the nodes that were active when saving the document. QMap assistantsFilenames; QList assistants; QMap keyframeFilenames; QStringList errorMessages; + QStringList warningMessages; }; void convertColorSpaceNames(QString &colorspacename, QString &profileProductName) { if (colorspacename == "Grayscale + Alpha") { colorspacename = "GRAYA"; profileProductName.clear(); } else if (colorspacename == "RgbAF32") { colorspacename = "RGBAF32"; profileProductName.clear(); } else if (colorspacename == "RgbAF16") { colorspacename = "RGBAF16"; profileProductName.clear(); } else if (colorspacename == "CMYKA16") { colorspacename = "CMYKAU16"; } else if (colorspacename == "GrayF32") { colorspacename = "GRAYAF32"; profileProductName.clear(); } else if (colorspacename == "GRAYA16") { colorspacename = "GRAYAU16"; } else if (colorspacename == "XyzAF16") { colorspacename = "XYZAF16"; profileProductName.clear(); } else if (colorspacename == "XyzAF32") { colorspacename = "XYZAF32"; profileProductName.clear(); } else if (colorspacename == "YCbCrA") { colorspacename = "YCBCRA8"; } else if (colorspacename == "YCbCrAU16") { colorspacename = "YCBCRAU16"; } } KisKraLoader::KisKraLoader(KisDocument * document, int syntaxVersion) : m_d(new Private()) { m_d->document = document; m_d->syntaxVersion = syntaxVersion; } KisKraLoader::~KisKraLoader() { delete m_d; } KisImageSP KisKraLoader::loadXML(const KoXmlElement& element) { QString attr; KisImageSP image = 0; QString name; qint32 width; qint32 height; QString profileProductName; double xres; double yres; QString colorspacename; const KoColorSpace * cs; if ((attr = element.attribute(MIME)) == NATIVE_MIMETYPE) { if ((m_d->imageName = element.attribute(NAME)).isNull()) { m_d->errorMessages << i18n("Image does not have a name."); return KisImageSP(0); } if ((attr = element.attribute(WIDTH)).isNull()) { m_d->errorMessages << i18n("Image does not specify a width."); return KisImageSP(0); } width = KisDomUtils::toInt(attr); if ((attr = element.attribute(HEIGHT)).isNull()) { m_d->errorMessages << i18n("Image does not specify a height."); return KisImageSP(0); } height = KisDomUtils::toInt(attr); m_d->imageComment = element.attribute(DESCRIPTION); xres = 100.0 / 72.0; if (!(attr = element.attribute(X_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { xres = value / 72.0; } } yres = 100.0 / 72.0; if (!(attr = element.attribute(Y_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { yres = value / 72.0; } } if ((colorspacename = element.attribute(COLORSPACE_NAME)).isNull()) { // An old file: take a reasonable default. // Krita didn't support anything else in those // days anyway. colorspacename = "RGBA"; } profileProductName = element.attribute(PROFILE); // A hack for an old colorspacename convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); if (profileProductName.isNull()) { // no mention of profile so get default profile"; cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, profileProductName); } if (cs == 0) { // try once more without the profile cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); if (cs == 0) { m_d->errorMessages << i18n("Image specifies an unsupported color model: %1.", colorspacename); return KisImageSP(0); } } KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); if (!(attr = element.attribute(PROOFINGPROFILENAME)).isNull()) { proofingConfig->proofingProfile = attr; } if (!(attr = element.attribute(PROOFINGMODEL)).isNull()) { proofingConfig->proofingModel = attr; } if (!(attr = element.attribute(PROOFINGDEPTH)).isNull()) { proofingConfig->proofingDepth = attr; } if (!(attr = element.attribute(PROOFINGINTENT)).isNull()) { proofingConfig->intent = (KoColorConversionTransformation::Intent) KisDomUtils::toInt(attr); } if (!(attr = element.attribute(PROOFINGADAPTATIONSTATE)).isNull()) { proofingConfig->adaptationState = KisDomUtils::toDouble(attr); } if (m_d->document) { image = new KisImage(m_d->document->createUndoStore(), width, height, cs, name); } else { image = new KisImage(0, width, height, cs, name); } image->setResolution(xres, yres); loadNodes(element, image, const_cast(image->rootLayer().data())); KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == CANVASPROJECTIONCOLOR) { if (e.hasAttribute(COLORBYTEDATA)) { QByteArray colorData = QByteArray::fromBase64(e.attribute(COLORBYTEDATA).toLatin1()); KoColor color((const quint8*)colorData.data(), image->colorSpace()); image->setDefaultProjectionColor(color); } } if(e.tagName()== PROOFINGWARNINGCOLOR) { QDomDocument dom; KoXml::asQDomElement(dom, e); QDomElement eq = dom.firstChildElement(); - proofingConfig->warningColor = KoColor::fromXML(eq.firstChildElement(), Integer8BitsColorDepthID.id(), QHash()); + proofingConfig->warningColor = KoColor::fromXML(eq.firstChildElement(), Integer8BitsColorDepthID.id()); } if (e.tagName().toLower() == "animation") { loadAnimationMetadata(e, image); } } image->setProofingConfiguration(proofingConfig); for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == "compositions") { loadCompositions(e, image); } } } KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if (e.tagName() == "grid") { loadGrid(e); } else if (e.tagName() == "guides") { loadGuides(e); } else if (e.tagName() == "assistants") { loadAssistantsList(e); } else if (e.tagName() == "audio") { loadAudio(e, image); } } return image; } void KisKraLoader::loadBinaryData(KoStore * store, KisImageSP image, const QString & uri, bool external) { // icc profile: if present, this overrides the profile product name loaded in loadXML. QString location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray data; data.resize(store->size()); bool res = (store->read(data.data(), store->size()) > -1); store->close(); if (res) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(image->colorSpace()->colorModelId().id(), image->colorSpace()->colorDepthId().id(), data); if (profile && profile->valid()) { res = image->assignImageProfile(profile); } if (!res) { profile = KoColorSpaceRegistry::instance()->profileByName(KoColorSpaceRegistry::instance()->colorSpaceFactory(image->colorSpace()->id())->defaultProfile()); Q_ASSERT(profile && profile->valid()); image->assignImageProfile(profile); } } } } //load the embed proofing profile, it only needs to be loaded into Krita, not assigned. location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray proofingData; proofingData.resize(store->size()); bool proofingProfileRes = (store->read(proofingData.data(), store->size())>-1); store->close(); if (proofingProfileRes) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(image->proofingConfiguration()->proofingModel, image->proofingConfiguration()->proofingDepth, proofingData); if (proofingProfile->valid()){ //if (KoColorSpaceEngineRegistry::instance()->get("icc")) { // KoColorSpaceEngineRegistry::instance()->get("icc")->addProfile(proofingProfile->fileName()); //} KoColorSpaceRegistry::instance()->addProfile(proofingProfile); } } } } // Load the layers data: if there is a profile associated with a layer it will be set now. KisKraLoadVisitor visitor(image, store, m_d->layerFilenames, m_d->keyframeFilenames, m_d->imageName, m_d->syntaxVersion); if (external) { visitor.setExternalUri(uri); } image->rootLayer()->accept(visitor); if (!visitor.errorMessages().isEmpty()) { m_d->errorMessages.append(visitor.errorMessages()); } + if (!visitor.warningMessages().isEmpty()) { + m_d->warningMessages.append(visitor.warningMessages()); + } // annotations // exif location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->hasFile(location)) { QByteArray data; store->open(location); data = store->read(store->size()); store->close(); image->addAnnotation(KisAnnotationSP(new KisAnnotation("exif", "", data))); } // layer styles location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->hasFile(location)) { KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource("Embedded Styles.asl"); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_d->imageName)); KIS_ASSERT_RECOVER_NOOP(!collection->valid()); store->open(location); { KoStoreDevice device(store); device.open(QIODevice::ReadOnly); /** * ASL loading code cannot work with non-sequential IO devices, * so convert the device beforehand! */ QByteArray buf = device.readAll(); QBuffer raDevice(&buf); raDevice.open(QIODevice::ReadOnly); collection->loadFromDevice(&raDevice); } store->close(); if (collection->valid()) { KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); collection->assignAllLayerStyles(image->root()); } else { warnKrita << "WARNING: Couldn't load layer styles library from .kra!"; delete collection; } } if (m_d->document && m_d->document->documentInfo()->aboutInfo("title").isNull()) m_d->document->documentInfo()->setAboutInfo("title", m_d->imageName); if (m_d->document && m_d->document->documentInfo()->aboutInfo("comment").isNull()) m_d->document->documentInfo()->setAboutInfo("comment", m_d->imageComment); loadAssistants(store, uri, external); } vKisNodeSP KisKraLoader::selectedNodes() const { return m_d->selectedNodes; } QList KisKraLoader::assistants() const { return m_d->assistants; } QStringList KisKraLoader::errorMessages() const { return m_d->errorMessages; } +QStringList KisKraLoader::warningMessages() const +{ + return m_d->warningMessages; +} + + void KisKraLoader::loadAssistants(KoStore *store, const QString &uri, bool external) { QString file_path; QString location; QMap handleMap; KisPaintingAssistant* assistant = 0; QMap::const_iterator loadedAssistant = m_d->assistantsFilenames.constBegin(); while (loadedAssistant != m_d->assistantsFilenames.constEnd()){ const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(loadedAssistant.value()); if (factory) { assistant = factory->createPaintingAssistant(); location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; file_path = location + loadedAssistant.key(); assistant->loadXml(store, handleMap, file_path); //If an assistant has too few handles than it should according to it's own setup, just don't load it// if (assistant->handles().size()==assistant->numHandles()){ m_d->assistants.append(toQShared(assistant)); } } loadedAssistant++; } } void KisKraLoader::loadAnimationMetadata(const KoXmlElement &element, KisImageSP image) { QDomDocument qDom; KoXml::asQDomElement(qDom, element); QDomElement qElement = qDom.firstChildElement(); float framerate; KisTimeRange range; int currentTime; KisImageAnimationInterface *animation = image->animationInterface(); if (KisDomUtils::loadValue(qElement, "framerate", &framerate)) { animation->setFramerate(framerate); } if (KisDomUtils::loadValue(qElement, "range", &range)) { animation->setFullClipRange(range); } if (KisDomUtils::loadValue(qElement, "currentTime", ¤tTime)) { animation->switchCurrentTimeAsync(currentTime); } } KisNodeSP KisKraLoader::loadNodes(const KoXmlElement& element, KisImageSP image, KisNodeSP parent) { KoXmlNode node = element.firstChild(); KoXmlNode child; if (!node.isNull()) { if (node.isElement()) { if (node.nodeName().toUpper() == LAYERS.toUpper() || node.nodeName().toUpper() == MASKS.toUpper()) { for (child = node.lastChild(); !child.isNull(); child = child.previousSibling()) { KisNodeSP node = loadNode(child.toElement(), image, parent); if (node) { image->nextLayerName(); // Make sure the nameserver is current with the number of nodes. image->addNode(node, parent); if (node->inherits("KisLayer") && child.childNodesCount() > 0) { loadNodes(child.toElement(), image, node); } } } } } } return parent; } KisNodeSP KisKraLoader::loadNode(const KoXmlElement& element, KisImageSP image, KisNodeSP parent) { // Nota bene: If you add new properties to layers, you should // ALWAYS define a default value in case the property is not // present in the layer definition: this helps a LOT with backward // compatibility. QString name = element.attribute(NAME, "No Name"); QUuid id = QUuid(element.attribute(UUID, QUuid().toString())); qint32 x = element.attribute(X, "0").toInt(); qint32 y = element.attribute(Y, "0").toInt(); qint32 opacity = element.attribute(OPACITY, QString::number(OPACITY_OPAQUE_U8)).toInt(); if (opacity < OPACITY_TRANSPARENT_U8) opacity = OPACITY_TRANSPARENT_U8; if (opacity > OPACITY_OPAQUE_U8) opacity = OPACITY_OPAQUE_U8; const KoColorSpace* colorSpace = 0; if ((element.attribute(COLORSPACE_NAME)).isNull()) { dbgFile << "No attribute color space for layer: " << name; colorSpace = image->colorSpace(); } else { QString colorspacename = element.attribute(COLORSPACE_NAME); QString profileProductName; convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); dbgFile << "Searching color space: " << colorspacename << colorspaceModel << colorspaceDepth << " for layer: " << name; // use default profile - it will be replaced later in completeLoading colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); dbgFile << "found colorspace" << colorSpace; if (!colorSpace) { - m_d->errorMessages << i18n("Layer %1 specifies an unsupported color model: %2.", name, colorspacename); + m_d->warningMessages << i18n("Layer %1 specifies an unsupported color model: %2.", name, colorspacename); return 0; } } const bool visible = element.attribute(VISIBLE, "1") == "0" ? false : true; const bool locked = element.attribute(LOCKED, "0") == "0" ? false : true; const bool collapsed = element.attribute(COLLAPSED, "0") == "0" ? false : true; int colorLabelIndex = element.attribute(COLOR_LABEL, "0").toInt(); QVector labels = KisNodeViewColorScheme::instance()->allColorLabels(); if (colorLabelIndex >= labels.size()) { colorLabelIndex = labels.size() - 1; } // Now find out the layer type and do specific handling QString nodeType; if (m_d->syntaxVersion == 1) { nodeType = element.attribute("layertype"); if (nodeType.isEmpty()) { nodeType = PAINT_LAYER; } } else { nodeType = element.attribute(NODE_TYPE); } if (nodeType.isEmpty()) { - m_d->errorMessages << i18n("Layer %1 has an unsupported type.", name); + m_d->warningMessages << i18n("Layer %1 has an unsupported type.", name); return 0; } KisNodeSP node = 0; if (nodeType == PAINT_LAYER) node = loadPaintLayer(element, image, name, colorSpace, opacity); else if (nodeType == GROUP_LAYER) node = loadGroupLayer(element, image, name, colorSpace, opacity); else if (nodeType == ADJUSTMENT_LAYER) node = loadAdjustmentLayer(element, image, name, colorSpace, opacity); else if (nodeType == SHAPE_LAYER) node = loadShapeLayer(element, image, name, colorSpace, opacity); else if (nodeType == GENERATOR_LAYER) node = loadGeneratorLayer(element, image, name, colorSpace, opacity); else if (nodeType == CLONE_LAYER) node = loadCloneLayer(element, image, name, colorSpace, opacity); else if (nodeType == FILTER_MASK) node = loadFilterMask(element, parent); else if (nodeType == TRANSFORM_MASK) node = loadTransformMask(element, parent); else if (nodeType == TRANSPARENCY_MASK) node = loadTransparencyMask(element, parent); else if (nodeType == SELECTION_MASK) node = loadSelectionMask(image, element, parent); else if (nodeType == COLORIZE_MASK) node = loadColorizeMask(image, element, parent, colorSpace); else if (nodeType == FILE_LAYER) { node = loadFileLayer(element, image, name, opacity); } else { - m_d->errorMessages << i18n("Layer %1 has an unsupported type: %2.", name, nodeType); + m_d->warningMessages << i18n("Layer %1 has an unsupported type: %2.", name, nodeType); return 0; } // Loading the node went wrong. Return empty node and leave to // upstream to complain to the user if (!node) { - m_d->errorMessages << i18n("Failure loading layer %1 of type: %2.", name, nodeType); + m_d->warningMessages << i18n("Failure loading layer %1 of type: %2.", name, nodeType); return 0; } node->setVisible(visible, true); node->setUserLocked(locked); node->setCollapsed(collapsed); node->setColorLabelIndex(colorLabelIndex); node->setX(x); node->setY(y); node->setName(name); if (! id.isNull()) // if no uuid in file, new one has been generated already node->setUuid(id); if (node->inherits("KisLayer") || node->inherits("KisColorizeMask")) { QString compositeOpName = element.attribute(COMPOSITE_OP, "normal"); node->setCompositeOpId(compositeOpName); } if (node->inherits("KisLayer")) { KisLayer* layer = qobject_cast(node.data()); QBitArray channelFlags = stringToFlags(element.attribute(CHANNEL_FLAGS, ""), colorSpace->channelCount()); layer->setChannelFlags(channelFlags); if (element.hasAttribute(LAYER_STYLE_UUID)) { QString uuidString = element.attribute(LAYER_STYLE_UUID); QUuid uuid(uuidString); if (!uuid.isNull()) { KisPSDLayerStyleSP dumbLayerStyle(new KisPSDLayerStyle()); dumbLayerStyle->setUuid(uuid); layer->setLayerStyle(dumbLayerStyle); } else { warnKrita << "WARNING: Layer style for layer" << layer->name() << "contains invalid UUID" << uuidString; } } } if (node->inherits("KisGroupLayer")) { if (element.hasAttribute(PASS_THROUGH_MODE)) { bool value = element.attribute(PASS_THROUGH_MODE, "0") != "0"; KisGroupLayer *group = qobject_cast(node.data()); group->setPassThroughMode(value); } } if (node->inherits("KisPaintLayer")) { KisPaintLayer* layer = qobject_cast(node.data()); QBitArray channelLockFlags = stringToFlags(element.attribute(CHANNEL_LOCK_FLAGS, ""), colorSpace->channelCount()); layer->setChannelLockFlags(channelLockFlags); bool onionEnabled = element.attribute(ONION_SKIN_ENABLED, "0") == "0" ? false : true; layer->setOnionSkinEnabled(onionEnabled); bool timelineEnabled = element.attribute(VISIBLE_IN_TIMELINE, "0") == "0" ? false : true; layer->setUseInTimeline(timelineEnabled); } if (element.attribute(FILE_NAME).isNull()) { m_d->layerFilenames[node.data()] = name; } else { m_d->layerFilenames[node.data()] = element.attribute(FILE_NAME); } if (element.hasAttribute("selected") && element.attribute("selected") == "true") { m_d->selectedNodes.append(node); } if (element.hasAttribute(KEYFRAME_FILE)) { m_d->keyframeFilenames.insert(node.data(), element.attribute(KEYFRAME_FILE)); } return node; } KisNodeSP KisKraLoader::loadPaintLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); KisPaintLayer* layer; layer = new KisPaintLayer(image, name, opacity, cs); Q_CHECK_PTR(layer); - - // Load exif info - /*TODO: write and use the legacy stuff to load that exif tag - for( KoXmlNode node = element.firstChild(); !node.isNull(); node = node.nextSibling() ) - { - KoXmlElement e = node.toElement(); - if ( !e.isNull() && e.tagName() == "ExifInfo" ) - { - layer->paintDevice()->exifInfo()->load(e); - } - }*/ - // TODO load metadata - return layer; } KisNodeSP KisKraLoader::loadFileLayer(const KoXmlElement& element, KisImageSP image, const QString& name, quint32 opacity) { QString filename = element.attribute("source", QString()); if (filename.isNull()) return 0; bool scale = (element.attribute("scale", "true") == "true"); int scalingMethod = element.attribute("scalingmethod", "-1").toInt(); if (scalingMethod < 0) { if (scale) { scalingMethod = KisFileLayer::ToImagePPI; } else { scalingMethod = KisFileLayer::None; } } QString documentPath; if (m_d->document) { documentPath = m_d->document->url().toLocalFile(); } QFileInfo info(documentPath); QString basePath = info.absolutePath(); QString fullPath = basePath + QDir::separator() + filename; // Entering the event loop to show the messagebox will delete the image, so up the ref by one image->ref(); if (!QFileInfo(fullPath).exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "The file associated to a file layer with the name \"%1\" is not found.\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", name, fullPath); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import)); dialog.setDefaultDir(basePath); QString url = dialog.filename(); if (!QFileInfo(basePath).exists()) { filename = url; } else { QDir d(basePath); filename = d.relativeFilePath(url); } } qApp->restoreOverrideCursor(); } KisLayer *layer = new KisFileLayer(image, basePath, filename, (KisFileLayer::ScalingMethod)scalingMethod, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGroupLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KisGroupLayer* layer; layer = new KisGroupLayer(image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadAdjustmentLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { // XXX: do something with filterversion? Q_UNUSED(cs); QString attr; KisAdjustmentLayer* layer; QString filtername; if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid adjustmentlayer! We should warn about it! warnFile << "No filter in adjustment layer"; return 0; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->defaultConfiguration(); // We'll load the configuration and the selection later. layer = new KisAdjustmentLayer(image, name, kfc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadShapeLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(element); Q_UNUSED(cs); QString attr; KoShapeBasedDocumentBase * shapeController = 0; if (m_d->document) { shapeController = m_d->document->shapeController(); } KisShapeLayer* layer = new KisShapeLayer(shapeController, image, name, opacity); Q_CHECK_PTR(layer); return layer; } KisNodeSP KisKraLoader::loadGeneratorLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); // XXX: do something with generator version? KisGeneratorLayer* layer; QString generatorname = element.attribute(GENERATOR_NAME); if (generatorname.isNull()) { // XXX: Invalid generator layer! We should warn about it! warnFile << "No generator in generator layer"; return 0; } KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorname); if (!generator) { warnFile << "No generator for generatorname" << generatorname << ""; return 0; // XXX: We don't have this generator. We should warn about it! } KisFilterConfigurationSP kgc = generator->defaultConfiguration(); // We'll load the configuration and the selection later. layer = new KisGeneratorLayer(image, name, kgc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadCloneLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); KisCloneLayerSP layer = new KisCloneLayer(0, image, name, opacity); KisCloneInfo info; if (! (element.attribute(CLONE_FROM_UUID)).isNull()) { info = KisCloneInfo(QUuid(element.attribute(CLONE_FROM_UUID))); } else { if ((element.attribute(CLONE_FROM)).isNull()) { return 0; } else { info = KisCloneInfo(element.attribute(CLONE_FROM)); } } layer->setCopyFromInfo(info); if ((element.attribute(CLONE_TYPE)).isNull()) { return 0; } else { layer->setCopyType((CopyLayerType) element.attribute(CLONE_TYPE).toInt()); } return layer; } KisNodeSP KisKraLoader::loadFilterMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(parent); QString attr; KisFilterMask* mask; QString filtername; // XXX: should we check the version? if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid filter layer! We should warn about it! warnFile << "No filter in filter layer"; return 0; } KisFilterSP f = KisFilterRegistry::instance()->value(filtername); if (!f) { warnFile << "No filter for filtername" << filtername << ""; return 0; // XXX: We don't have this filter. We should warn about it! } KisFilterConfigurationSP kfc = f->defaultConfiguration(); // We'll load the configuration and the selection later. mask = new KisFilterMask(); mask->setFilter(kfc); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransformMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(element); Q_UNUSED(parent); KisTransformMask* mask; /** * We'll load the transform configuration later on a stage * of binary data loading */ mask = new KisTransformMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransparencyMask(const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(element); Q_UNUSED(parent); KisTransparencyMask* mask = new KisTransparencyMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadSelectionMask(KisImageSP image, const KoXmlElement& element, KisNodeSP parent) { Q_UNUSED(parent); KisSelectionMaskSP mask = new KisSelectionMask(image); bool active = element.attribute(ACTIVE, "1") == "0" ? false : true; mask->setActive(active); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadColorizeMask(KisImageSP image, const KoXmlElement& element, KisNodeSP parent, const KoColorSpace *colorSpace) { Q_UNUSED(parent); KisColorizeMaskSP mask = new KisColorizeMask(); bool editKeystrokes = element.attribute(COLORIZE_EDIT_KEYSTROKES, "1") == "0" ? false : true; bool showColoring = element.attribute(COLORIZE_SHOW_COLORING, "1") == "0" ? false : true; KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, editKeystrokes, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, showColoring, image); delete mask->setColorSpace(colorSpace); mask->setImage(image); return mask; } void KisKraLoader::loadCompositions(const KoXmlElement& elem, KisImageSP image) { KoXmlNode child; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString name = e.attribute("name"); bool exportEnabled = e.attribute("exportEnabled", "1") == "0" ? false : true; KisLayerCompositionSP composition(new KisLayerComposition(image, name)); composition->setExportEnabled(exportEnabled); KoXmlNode value; for (value = child.lastChild(); !value.isNull(); value = value.previousSibling()) { KoXmlElement e = value.toElement(); QUuid uuid(e.attribute("uuid")); bool visible = e.attribute("visible", "1") == "0" ? false : true; composition->setVisible(uuid, visible); bool collapsed = e.attribute("collapsed", "1") == "0" ? false : true; composition->setCollapsed(uuid, collapsed); } image->addComposition(composition); } } void KisKraLoader::loadAssistantsList(const KoXmlElement &elem) { KoXmlNode child; int count = 0; for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) { KoXmlElement e = child.toElement(); QString type = e.attribute("type"); QString file_name = e.attribute("filename"); m_d->assistantsFilenames.insert(file_name,type); count++; } } void KisKraLoader::loadGrid(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGridConfig config; config.loadDynamicDataFromXml(domElement); config.loadStaticData(); m_d->document->setGridConfig(config); } void KisKraLoader::loadGuides(const KoXmlElement& elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisGuidesConfig guides; guides.loadFromXml(domElement); m_d->document->setGuidesConfig(guides); } void KisKraLoader::loadAudio(const KoXmlElement& elem, KisImageSP image) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement qElement = dom.firstChildElement(); QString fileName; if (KisDomUtils::loadValue(qElement, "masterChannelPath", &fileName)) { fileName = QDir::toNativeSeparators(fileName); QDir baseDirectory = QFileInfo(m_d->document->localFilePath()).absoluteDir(); fileName = baseDirectory.absoluteFilePath(fileName); QFileInfo info(fileName); if (!info.exists()) { qApp->setOverrideCursor(Qt::ArrowCursor); QString msg = i18nc( "@info", "Audio channel file \"%1\" doesn't exist!\n\n" "Expected path:\n" "%2\n\n" "Do you want to locate it manually?", info.fileName(), info.absoluteFilePath()); int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (result == QMessageBox::Yes) { info.setFile(KisImportExportManager::askForAudioFileName(info.absolutePath(), 0)); } qApp->restoreOverrideCursor(); } if (info.exists()) { image->animationInterface()->setAudioChannelFileName(info.absoluteFilePath()); } } bool audioMuted = false; if (KisDomUtils::loadValue(qElement, "audioMuted", &audioMuted)) { image->animationInterface()->setAudioMuted(audioMuted); } qreal audioVolume = 0.5; if (KisDomUtils::loadValue(qElement, "audioVolume", &audioVolume)) { image->animationInterface()->setAudioVolume(audioVolume); } } diff --git a/plugins/impex/libkra/kis_kra_loader.h b/plugins/impex/libkra/kis_kra_loader.h index 789cfdad2d..3e8168088f 100644 --- a/plugins/impex/libkra/kis_kra_loader.h +++ b/plugins/impex/libkra/kis_kra_loader.h @@ -1,114 +1,117 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2007 * * 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_KRA_LOADER_H #define KIS_KRA_LOADER_H class QString; class QStringList; #include "KoXmlReaderForward.h" class KoStore; class KisDocument; class KoColorSpace; class KisPaintingAssistant; #include #include "kritalibkra_export.h" /** * Load old-style 1.x .kra files. Updated for 2.0, let's try to stay * compatible. But 2.0 won't be able to save 1.x .kra files unless we * implement an export filter. */ class KRITALIBKRA_EXPORT KisKraLoader { public: KisKraLoader(KisDocument * document, int syntaxVersion); ~KisKraLoader(); /** * Loading is done in two steps: first all xml is loaded, then, in finishLoading, * the actual layer data is loaded. */ KisImageSP loadXML(const KoXmlElement& elem); void loadBinaryData(KoStore* store, KisImageSP image, const QString & uri, bool external); vKisNodeSP selectedNodes() const; // it's neater to follow the same design as with selectedNodes, so let's have a getter here QList assistants() const; /// if empty, loading didn't fail... QStringList errorMessages() const; + /// if not empty, loading didn't fail, but there are problems + QStringList warningMessages() const; + private: // this needs to be private, for neatness sake void loadAssistants(KoStore* store, const QString & uri, bool external); void loadAnimationMetadata(const KoXmlElement& element, KisImageSP image); KisNodeSP loadNodes(const KoXmlElement& element, KisImageSP image, KisNodeSP parent); KisNodeSP loadNode(const KoXmlElement& elem, KisImageSP image, KisNodeSP parent); KisNodeSP loadPaintLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity); KisNodeSP loadGroupLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity); KisNodeSP loadAdjustmentLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity); KisNodeSP loadShapeLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity); KisNodeSP loadGeneratorLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity); KisNodeSP loadCloneLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity); KisNodeSP loadFilterMask(const KoXmlElement& elem, KisNodeSP parent); KisNodeSP loadTransformMask(const KoXmlElement& elem, KisNodeSP parent); KisNodeSP loadTransparencyMask(const KoXmlElement& elem, KisNodeSP parent); KisNodeSP loadSelectionMask(KisImageSP image, const KoXmlElement& elem, KisNodeSP parent); KisNodeSP loadColorizeMask(KisImageSP image, const KoXmlElement& elem, KisNodeSP parent, const KoColorSpace *colorSpace); KisNodeSP loadFileLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, quint32 opacity); void loadNodeKeyframes(KoStore *store, const QString &location, KisNodeSP node); void loadCompositions(const KoXmlElement& elem, KisImageSP image); void loadAssistantsList(const KoXmlElement& elem); void loadGrid(const KoXmlElement& elem); void loadGuides(const KoXmlElement& elem); void loadAudio(const KoXmlElement& elem, KisImageSP image); private: struct Private; Private * const m_d; }; #endif diff --git a/plugins/impex/tiff/tests/CMakeLists.txt b/plugins/impex/tiff/tests/CMakeLists.txt index 9c7bbea85c..22a2bdef0e 100644 --- a/plugins/impex/tiff/tests/CMakeLists.txt +++ b/plugins/impex/tiff/tests/CMakeLists.txt @@ -1,14 +1,16 @@ if(LCMS2_FOUND) add_definitions( -DHAVE_LCMS2 ) endif() set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) +include(ECMAddTests) include(KritaAddBrokenUnitTest) - macro_add_unittest_definitions() -krita_add_broken_unit_test(kis_tiff_test.cpp - TEST_NAME krita-plugin-format-tiff_test - LINK_LIBRARIES kritaui Qt5::Test) +ecm_add_tests( + kis_tiff_test.cpp + NAME_PREFIX "krita-plugin-impex-tiff-" + LINK_LIBRARIES kritaui Qt5::Test +) diff --git a/plugins/impex/tiff/tests/kis_tiff_test.cpp b/plugins/impex/tiff/tests/kis_tiff_test.cpp index eacb5bc862..14edede186 100644 --- a/plugins/impex/tiff/tests/kis_tiff_test.cpp +++ b/plugins/impex/tiff/tests/kis_tiff_test.cpp @@ -1,115 +1,120 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tiff_test.h" #include #include #include #include "filestest.h" #include #include #include "kisexiv2/kis_exiv2.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif void KisTiffTest::testFiles() { // XXX: make the exiv io backends real plugins KisExiv2::initialize(); QStringList excludes; #ifndef CPU_32_BITS excludes << "flower-minisblack-06.tif"; #endif - + #ifdef HAVE_LCMS2 excludes << "flower-separated-contig-08.tif" << "flower-separated-contig-16.tif" << "flower-separated-planar-08.tif" << "flower-separated-planar-16.tif" + << "flower-minisblack-02.tif" + << "flower-minisblack-04.tif" + << "flower-minisblack-08.tif" << "flower-minisblack-10.tif" << "flower-minisblack-12.tif" << "flower-minisblack-14.tif" << "flower-minisblack-16.tif" << "flower-minisblack-24.tif" << "flower-minisblack-32.tif" + << "jim___dg.tif" + << "jim___gg.tif" << "strike.tif"; #endif excludes << "text.tif" << "ycbcr-cat.tif"; TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", excludes); } void KisTiffTest::testRoundTripRGBF16() { // Disabled for now, it's broken because we assumed integers. #if 0 QRect testRect(0,0,1000,1000); QRect fillRect(100,100,100,100); const KoColorSpace *csf16 = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float16BitsColorDepthID.id(), 0); KisDocument *doc0 = qobject_cast(KisPart::instance()->createDocument()); doc0->newImage("test", testRect.width(), testRect.height(), csf16, KoColor(Qt::blue, csf16), QString(), 1.0); QTemporaryFile tmpFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".tiff")); tmpFile.open(); doc0->setBackupFile(false); doc0->setOutputMimeType("image/tiff"); doc0->setFileBatchMode(true); doc0->saveAs(QUrl::fromLocalFile(tmpFile.fileName())); KisNodeSP layer0 = doc0->image()->root()->firstChild(); Q_ASSERT(layer0); layer0->paintDevice()->fill(fillRect, KoColor(Qt::red, csf16)); KisDocument *doc1 = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc1); manager.setBatchMode(false); KisImportExportFilter::ConversionStatus status; QString s = manager.importDocument(tmpFile.fileName(), QString(), status); dbgKrita << s; Q_ASSERT(doc1->image()); QImage ref0 = doc0->image()->projection()->convertToQImage(0, testRect); QImage ref1 = doc1->image()->projection()->convertToQImage(0, testRect); QCOMPARE(ref0, ref1); #endif } QTEST_MAIN(KisTiffTest) diff --git a/plugins/paintops/libpaintop/kis_dynamic_sensor.cc b/plugins/paintops/libpaintop/kis_dynamic_sensor.cc index 803148aa2c..09c18c5215 100644 --- a/plugins/paintops/libpaintop/kis_dynamic_sensor.cc +++ b/plugins/paintops/libpaintop/kis_dynamic_sensor.cc @@ -1,478 +1,477 @@ /* * Copyright (c) 2007 Cyrille Berger * Copyright (c) 2011 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dynamic_sensor.h" #include #include "kis_algebra_2d.h" #include "sensors/kis_dynamic_sensors.h" #include "sensors/kis_dynamic_sensor_distance.h" #include "sensors/kis_dynamic_sensor_drawing_angle.h" #include "sensors/kis_dynamic_sensor_time.h" #include "sensors/kis_dynamic_sensor_fade.h" #include "sensors/kis_dynamic_sensor_fuzzy.h" KisDynamicSensor::KisDynamicSensor(DynamicSensorType type) : m_length(-1) , m_type(type) , m_customCurve(false) , m_active(false) { } KisDynamicSensor::~KisDynamicSensor() { } QWidget* KisDynamicSensor::createConfigurationWidget(QWidget* parent, QWidget*) { Q_UNUSED(parent); return 0; } void KisDynamicSensor::reset() { } KisDynamicSensorSP KisDynamicSensor::id2Sensor(const KoID& id) { if (id.id() == PressureId.id()) { return new KisDynamicSensorPressure(); } else if (id.id() == PressureInId.id()) { return new KisDynamicSensorPressureIn(); } else if (id.id() == XTiltId.id()) { return new KisDynamicSensorXTilt(); } else if (id.id() == YTiltId.id()) { return new KisDynamicSensorYTilt(); } else if (id.id() == TiltDirectionId.id()) { return new KisDynamicSensorTiltDirection(); } else if (id.id() == TiltElevationId.id()) { return new KisDynamicSensorTiltElevation(); } else if (id.id() == SpeedId.id()) { return new KisDynamicSensorSpeed(); } else if (id.id() == DrawingAngleId.id()) { return new KisDynamicSensorDrawingAngle(); } else if (id.id() == RotationId.id()) { return new KisDynamicSensorRotation(); } else if (id.id() == DistanceId.id()) { return new KisDynamicSensorDistance(); } else if (id.id() == TimeId.id()) { return new KisDynamicSensorTime(); } else if (id.id() == FuzzyPerDabId.id()) { return new KisDynamicSensorFuzzy(false); } else if (id.id() == FuzzyPerStrokeId.id()) { return new KisDynamicSensorFuzzy(true); } else if (id.id() == FadeId.id()) { return new KisDynamicSensorFade(); } else if (id.id() == PerspectiveId.id()) { return new KisDynamicSensorPerspective(); } else if (id.id() == TangentialPressureId.id()) { return new KisDynamicSensorTangentialPressure(); } dbgPlugins << "Unknown transform parameter :" << id.id(); return 0; } DynamicSensorType KisDynamicSensor::id2Type(const KoID &id) { if (id.id() == PressureId.id()) { return PRESSURE; } else if (id.id() == PressureInId.id()) { return PRESSURE_IN; } else if (id.id() == XTiltId.id()) { return XTILT; } else if (id.id() == YTiltId.id()) { return YTILT; } else if (id.id() == TiltDirectionId.id()) { return TILT_DIRECTION; } else if (id.id() == TiltElevationId.id()) { return TILT_ELEVATATION; } else if (id.id() == SpeedId.id()) { return SPEED; } else if (id.id() == DrawingAngleId.id()) { return ANGLE; } else if (id.id() == RotationId.id()) { return ROTATION; } else if (id.id() == DistanceId.id()) { return DISTANCE; } else if (id.id() == TimeId.id()) { return TIME; } else if (id.id() == FuzzyPerDabId.id()) { return FUZZY_PER_DAB; } else if (id.id() == FuzzyPerStrokeId.id()) { return FUZZY_PER_STROKE; } else if (id.id() == FadeId.id()) { return FADE; } else if (id.id() == PerspectiveId.id()) { return PERSPECTIVE; } else if (id.id() == TangentialPressureId.id()) { return TANGENTIAL_PRESSURE; } return UNKNOWN; } KisDynamicSensorSP KisDynamicSensor::type2Sensor(DynamicSensorType sensorType) { switch (sensorType) { case FUZZY_PER_DAB: return new KisDynamicSensorFuzzy(false); case FUZZY_PER_STROKE: return new KisDynamicSensorFuzzy(true); case SPEED: return new KisDynamicSensorSpeed(); case FADE: return new KisDynamicSensorFade(); case DISTANCE: return new KisDynamicSensorDistance(); case TIME: return new KisDynamicSensorTime(); case ANGLE: return new KisDynamicSensorDrawingAngle(); case ROTATION: return new KisDynamicSensorRotation(); case PRESSURE: return new KisDynamicSensorPressure(); case XTILT: return new KisDynamicSensorXTilt(); case YTILT: return new KisDynamicSensorYTilt(); case TILT_DIRECTION: return new KisDynamicSensorTiltDirection(); case TILT_ELEVATATION: return new KisDynamicSensorTiltElevation(); case PERSPECTIVE: return new KisDynamicSensorPerspective(); case TANGENTIAL_PRESSURE: return new KisDynamicSensorTangentialPressure(); case PRESSURE_IN: return new KisDynamicSensorPressureIn(); default: return 0; } } QString KisDynamicSensor::minimumLabel(DynamicSensorType sensorType) { switch (sensorType) { case FUZZY_PER_DAB: case FUZZY_PER_STROKE: return QString(); case FADE: return i18n("0"); case DISTANCE: return i18n("0 px"); case TIME: return i18n("0 s"); case ANGLE: return i18n("0°"); case SPEED: return i18n("Slow"); case ROTATION: return i18n("0°"); case PRESSURE: return i18n("Low"); case XTILT: return i18n("-30°"); case YTILT: return i18n("-30°"); case TILT_DIRECTION: return i18n("0°"); case TILT_ELEVATATION: return i18n("90°"); case PERSPECTIVE: return i18n("Far"); case TANGENTIAL_PRESSURE: case PRESSURE_IN: return i18n("Low"); default: return i18n("0.0"); } } QString KisDynamicSensor::maximumLabel(DynamicSensorType sensorType, int max) { switch (sensorType) { case FUZZY_PER_DAB: case FUZZY_PER_STROKE: return QString(); case FADE: if (max < 0) return i18n("1000"); else return i18n("%1", max); case DISTANCE: if (max < 0) return i18n("30 px"); else return i18n("%1 px", max); case TIME: if (max < 0) return i18n("3 s"); else return i18n("%1 s", max / 1000); case ANGLE: return i18n("360°"); case SPEED: return i18n("Fast"); case ROTATION: return i18n("360°"); case PRESSURE: return i18n("High"); case XTILT: return i18n("30°"); case YTILT: return i18n("30°"); case TILT_DIRECTION: return i18n("360°"); case TILT_ELEVATATION: return i18n("0°"); case PERSPECTIVE: return i18n("Near"); case TANGENTIAL_PRESSURE: - case PRESSURE_IN: + case PRESSURE_IN: return i18n("High"); default: return i18n("1.0"); }; } KisDynamicSensorSP KisDynamicSensor::createFromXML(const QString& s) { QDomDocument doc; doc.setContent(s); QDomElement e = doc.documentElement(); return createFromXML(e); } KisDynamicSensorSP KisDynamicSensor::createFromXML(const QDomElement& e) { QString id = e.attribute("id", ""); KisDynamicSensorSP sensor = id2Sensor(id); if (sensor) { sensor->fromXML(e); } return sensor; } QList KisDynamicSensor::sensorsIds() { QList ids; ids << PressureId << PressureInId << XTiltId << YTiltId << TiltDirectionId << TiltElevationId << SpeedId << DrawingAngleId << RotationId << DistanceId << TimeId << FuzzyPerDabId << FuzzyPerStrokeId << FadeId << PerspectiveId << TangentialPressureId; return ids; } QList KisDynamicSensor::sensorsTypes() { QList sensorTypes; sensorTypes << PRESSURE << PRESSURE_IN << XTILT << YTILT << TILT_DIRECTION << TILT_ELEVATATION << SPEED << ANGLE << ROTATION << DISTANCE << TIME << FUZZY_PER_DAB << FUZZY_PER_STROKE << FADE << PERSPECTIVE << TANGENTIAL_PRESSURE; return sensorTypes; } QString KisDynamicSensor::id(DynamicSensorType sensorType) { switch (sensorType) { case FUZZY_PER_DAB: return "fuzzy"; case FUZZY_PER_STROKE: return "fuzzystroke"; case FADE: return "fade"; case DISTANCE: return "distance"; case TIME: return "time"; case ANGLE: return "drawingangle"; case SPEED: return "speed"; case ROTATION: return "rotation"; case PRESSURE: return "pressure"; case XTILT: return "xtilt"; case YTILT: return "ytilt"; case TILT_DIRECTION: return "ascension"; case TILT_ELEVATATION: return "declination"; case PERSPECTIVE: return "perspective"; case TANGENTIAL_PRESSURE: return "tangentialpressure"; case PRESSURE_IN: return "pressurein"; case SENSORS_LIST: return "sensorslist"; default: return QString(); }; } void KisDynamicSensor::toXML(QDomDocument& doc, QDomElement& elt) const { elt.setAttribute("id", id(sensorType())); if (m_customCurve) { QDomElement curve_elt = doc.createElement("curve"); QDomText text = doc.createTextNode(m_curve.toString()); curve_elt.appendChild(text); elt.appendChild(curve_elt); } } void KisDynamicSensor::fromXML(const QDomElement& e) { - Q_UNUSED(e); Q_ASSERT(e.attribute("id", "") == id(sensorType())); m_customCurve = false; QDomElement curve_elt = e.firstChildElement("curve"); if (!curve_elt.isNull()) { m_customCurve = true; m_curve.fromString(curve_elt.text()); } } qreal KisDynamicSensor::parameter(const KisPaintInformation& info) { const qreal val = value(info); if (m_customCurve) { qreal scaledVal = isAdditive() ? additiveToScaling(val) : val; int offset = qRound(256.0 * qAbs(scaledVal)); qreal newValue = m_curve.floatTransfer(257)[qBound(0, offset, 256)]; scaledVal = KisAlgebra2D::copysign(newValue, scaledVal); return isAdditive() ? scalingToAdditive(scaledVal) : scaledVal; } else { return val; } } void KisDynamicSensor::setCurve(const KisCubicCurve& curve) { m_customCurve = true; m_curve = curve; } const KisCubicCurve& KisDynamicSensor::curve() const { return m_curve; } void KisDynamicSensor::removeCurve() { m_customCurve = false; } bool KisDynamicSensor::hasCustomCurve() const { return m_customCurve; } bool KisDynamicSensor::dependsOnCanvasRotation() const { return true; } bool KisDynamicSensor::isAdditive() const { return false; } bool KisDynamicSensor::isAbsoluteRotation() const { return false; } void KisDynamicSensor::setActive(bool active) { m_active = active; } bool KisDynamicSensor::isActive() const { return m_active; } diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp index c018349fab..02e5e2f2cf 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp +++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp @@ -1,1236 +1,1242 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2008-2009 Jan Hambrecht Copyright (C) 2008 C. Boemann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "DefaultTool.h" #include "DefaultToolWidget.h" #include "DefaultToolArrangeWidget.h" #include "SelectionDecorator.h" #include "ShapeMoveStrategy.h" #include "ShapeRotateStrategy.h" #include "ShapeShearStrategy.h" #include "ShapeResizeStrategy.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_action_registry.h" #include +#include #include #include #include #include #include #include #define HANDLE_DISTANCE 10 class NopInteractionStrategy : public KoInteractionStrategy { public: - explicit NopInteractionStrategy(KoToolBase *parent) - : KoInteractionStrategy(parent) + explicit NopInteractionStrategy(KoToolBase *parent) + : KoInteractionStrategy(parent) { } KUndo2Command *createCommand() override { return 0; } void handleMouseMove(const QPointF & /*mouseLocation*/, Qt::KeyboardModifiers /*modifiers*/) override {} void finishInteraction(Qt::KeyboardModifiers /*modifiers*/) override {} }; class SelectionHandler : public KoToolSelection { public: SelectionHandler(DefaultTool *parent) : KoToolSelection(parent) , m_selection(parent->koSelection()) { - Q_ASSERT(m_selection); } bool hasSelection() override { - return m_selection->count(); + if (m_selection) { + return m_selection->count(); + } + return false; } private: - KoSelection *m_selection; + QPointer m_selection; }; DefaultTool::DefaultTool(KoCanvasBase *canvas) : KoInteractionTool(canvas) , m_lastHandle(KoFlake::NoHandle) , m_hotPosition(KoFlake::TopLeftCorner) , m_mouseWasInsideHandles(false) , m_moveCommand(0) , m_selectionHandler(new SelectionHandler(this)) , m_customEventStrategy(0) { setupActions(); QPixmap rotatePixmap, shearPixmap; rotatePixmap.load(":/cursor_rotate.png"); Q_ASSERT(!rotatePixmap.isNull()); shearPixmap.load(":/cursor_shear.png"); Q_ASSERT(!shearPixmap.isNull()); m_rotateCursors[0] = QCursor(rotatePixmap.transformed(QTransform().rotate(45))); m_rotateCursors[1] = QCursor(rotatePixmap.transformed(QTransform().rotate(90))); m_rotateCursors[2] = QCursor(rotatePixmap.transformed(QTransform().rotate(135))); m_rotateCursors[3] = QCursor(rotatePixmap.transformed(QTransform().rotate(180))); m_rotateCursors[4] = QCursor(rotatePixmap.transformed(QTransform().rotate(225))); m_rotateCursors[5] = QCursor(rotatePixmap.transformed(QTransform().rotate(270))); m_rotateCursors[6] = QCursor(rotatePixmap.transformed(QTransform().rotate(315))); m_rotateCursors[7] = QCursor(rotatePixmap); /* m_rotateCursors[0] = QCursor(Qt::RotateNCursor); m_rotateCursors[1] = QCursor(Qt::RotateNECursor); m_rotateCursors[2] = QCursor(Qt::RotateECursor); m_rotateCursors[3] = QCursor(Qt::RotateSECursor); m_rotateCursors[4] = QCursor(Qt::RotateSCursor); m_rotateCursors[5] = QCursor(Qt::RotateSWCursor); m_rotateCursors[6] = QCursor(Qt::RotateWCursor); m_rotateCursors[7] = QCursor(Qt::RotateNWCursor); */ m_shearCursors[0] = QCursor(shearPixmap); m_shearCursors[1] = QCursor(shearPixmap.transformed(QTransform().rotate(45))); m_shearCursors[2] = QCursor(shearPixmap.transformed(QTransform().rotate(90))); m_shearCursors[3] = QCursor(shearPixmap.transformed(QTransform().rotate(135))); m_shearCursors[4] = QCursor(shearPixmap.transformed(QTransform().rotate(180))); m_shearCursors[5] = QCursor(shearPixmap.transformed(QTransform().rotate(225))); m_shearCursors[6] = QCursor(shearPixmap.transformed(QTransform().rotate(270))); m_shearCursors[7] = QCursor(shearPixmap.transformed(QTransform().rotate(315))); m_sizeCursors[0] = Qt::SizeVerCursor; m_sizeCursors[1] = Qt::SizeBDiagCursor; m_sizeCursors[2] = Qt::SizeHorCursor; m_sizeCursors[3] = Qt::SizeFDiagCursor; m_sizeCursors[4] = Qt::SizeVerCursor; m_sizeCursors[5] = Qt::SizeBDiagCursor; m_sizeCursors[6] = Qt::SizeHorCursor; m_sizeCursors[7] = Qt::SizeFDiagCursor; KoShapeManager *manager = canvas->shapeManager(); connect(manager, SIGNAL(selectionChanged()), this, SLOT(updateActions())); } DefaultTool::~DefaultTool() { } bool DefaultTool::wantsAutoScroll() const { return true; } void DefaultTool::setupActions() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QAction *actionBringToFront = actionRegistry->makeQAction("object_order_front", this); addAction("object_order_front", actionBringToFront); connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront())); QAction *actionRaise = actionRegistry->makeQAction("object_order_raise", this); addAction("object_order_raise", actionRaise); connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp())); QAction *actionLower = actionRegistry->makeQAction("object_order_lower", this); addAction("object_order_lower", actionLower); connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown())); QAction *actionSendToBack = actionRegistry->makeQAction("object_order_back", this); addAction("object_order_back", actionSendToBack); connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack())); QAction *actionAlignLeft = actionRegistry->makeQAction("object_align_horizontal_left", this); addAction("object_align_horizontal_left", actionAlignLeft); connect(actionAlignLeft, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalLeft())); QAction *actionAlignCenter = actionRegistry->makeQAction("object_align_horizontal_center", this); addAction("object_align_horizontal_center", actionAlignCenter); connect(actionAlignCenter, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalCenter())); QAction *actionAlignRight = actionRegistry->makeQAction("object_align_horizontal_right", this); addAction("object_align_horizontal_right", actionAlignRight); connect(actionAlignRight, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalRight())); QAction *actionAlignTop = actionRegistry->makeQAction("object_align_vertical_top", this); addAction("object_align_vertical_top", actionAlignTop); connect(actionAlignTop, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalTop())); QAction *actionAlignMiddle = actionRegistry->makeQAction("object_align_vertical_center", this); addAction("object_align_vertical_center", actionAlignMiddle); connect(actionAlignMiddle, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalCenter())); QAction *actionAlignBottom = actionRegistry->makeQAction("object_align_vertical_bottom", this); addAction("object_align_vertical_bottom", actionAlignBottom); connect(actionAlignBottom, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalBottom())); QAction *actionGroupBottom = actionRegistry->makeQAction("object_group", this); addAction("object_group", actionGroupBottom); connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup())); QAction *actionUngroupBottom = actionRegistry->makeQAction("object_ungroup", this); addAction("object_ungroup", actionUngroupBottom); connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup())); } qreal DefaultTool::rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation) { QPointF selectionCenter = koSelection()->absolutePosition(); QPointF direction; switch (handle) { case KoFlake::TopMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopRightCorner) - koSelection()->absolutePosition(KoFlake::TopLeftCorner); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeftCorner); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRightCorner) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRightCorner) - koSelection()->absolutePosition(KoFlake::TopLeftCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRightCorner) - koSelection()->absolutePosition(KoFlake::BottomRightCorner)).normalized()).toPointF(); break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomRightCorner) - koSelection()->absolutePosition(KoFlake::TopRightCorner); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRightCorner); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRightCorner) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomRightHandle: direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRightCorner) - koSelection()->absolutePosition(KoFlake::BottomLeftCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRightCorner) - koSelection()->absolutePosition(KoFlake::TopRightCorner)).normalized()).toPointF(); break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomRightCorner); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeftCorner); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRightCorner) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::BottomLeftHandle: direction = koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomRightCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - koSelection()->absolutePosition(KoFlake::TopLeftCorner)).normalized()).toPointF(); break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { direction = koSelection()->absolutePosition(KoFlake::TopLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomLeftCorner); } else { QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeftCorner); handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - handlePosition); direction = handlePosition - selectionCenter; } break; case KoFlake::TopLeftHandle: direction = koSelection()->absolutePosition(KoFlake::TopLeftCorner) - selectionCenter; direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeftCorner) - koSelection()->absolutePosition(KoFlake::TopRightCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomLeftCorner)).normalized()).toPointF(); break; case KoFlake::NoHandle: return 0.0; break; } qreal rotation = atan2(direction.y(), direction.x()) * 180.0 / M_PI; switch (handle) { case KoFlake::TopMiddleHandle: if (useEdgeRotation) { rotation -= 0.0; } else { rotation -= 270.0; } break; case KoFlake::TopRightHandle: rotation -= 315.0; break; case KoFlake::RightMiddleHandle: if (useEdgeRotation) { rotation -= 90.0; } else { rotation -= 0.0; } break; case KoFlake::BottomRightHandle: rotation -= 45.0; break; case KoFlake::BottomMiddleHandle: if (useEdgeRotation) { rotation -= 180.0; } else { rotation -= 90.0; } break; case KoFlake::BottomLeftHandle: rotation -= 135.0; break; case KoFlake::LeftMiddleHandle: if (useEdgeRotation) { rotation -= 270.0; } else { rotation -= 180.0; } break; case KoFlake::TopLeftHandle: rotation -= 225.0; break; case KoFlake::NoHandle: break; } if (rotation < 0.0) { rotation += 360.0; } return rotation; } void DefaultTool::updateCursor() { QCursor cursor = Qt::ArrowCursor; QString statusText; - - if (koSelection()->count() > 0) { // has a selection + KoSelection * selection = koSelection(); + if (selection && selection->count() > 0) { // has a selection bool editable = editableShapesCount(koSelection()->selectedShapes(KoFlake::StrippedSelection)); if (!m_mouseWasInsideHandles) { m_angle = rotationOfHandle(m_lastHandle, true); int rotOctant = 8 + int(8.5 + m_angle / 45); bool rotateHandle = false; bool shearHandle = false; switch (m_lastHandle) { case KoFlake::TopMiddleHandle: cursor = m_shearCursors[(0 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::TopRightHandle: cursor = m_rotateCursors[(1 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::RightMiddleHandle: cursor = m_shearCursors[(2 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::BottomRightHandle: cursor = m_rotateCursors[(3 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::BottomMiddleHandle: cursor = m_shearCursors[(4 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::BottomLeftHandle: cursor = m_rotateCursors[(5 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::LeftMiddleHandle: cursor = m_shearCursors[(6 + rotOctant) % 8]; shearHandle = true; break; case KoFlake::TopLeftHandle: cursor = m_rotateCursors[(7 + rotOctant) % 8]; rotateHandle = true; break; case KoFlake::NoHandle: cursor = Qt::ArrowCursor; break; } if (rotateHandle) { statusText = i18n("Left click rotates around center, right click around highlighted position."); } if (shearHandle) { statusText = i18n("Click and drag to shear selection."); } } else { statusText = i18n("Click and drag to resize selection."); m_angle = rotationOfHandle(m_lastHandle, false); int rotOctant = 8 + int(8.5 + m_angle / 45); bool cornerHandle = false; switch (m_lastHandle) { case KoFlake::TopMiddleHandle: cursor = m_sizeCursors[(0 + rotOctant) % 8]; break; case KoFlake::TopRightHandle: cursor = m_sizeCursors[(1 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::RightMiddleHandle: cursor = m_sizeCursors[(2 + rotOctant) % 8]; break; case KoFlake::BottomRightHandle: cursor = m_sizeCursors[(3 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::BottomMiddleHandle: cursor = m_sizeCursors[(4 + rotOctant) % 8]; break; case KoFlake::BottomLeftHandle: cursor = m_sizeCursors[(5 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::LeftMiddleHandle: cursor = m_sizeCursors[(6 + rotOctant) % 8]; break; case KoFlake::TopLeftHandle: cursor = m_sizeCursors[(7 + rotOctant) % 8]; cornerHandle = true; break; case KoFlake::NoHandle: cursor = Qt::SizeAllCursor; statusText = i18n("Click and drag to move selection."); break; } if (cornerHandle) { statusText = i18n("Click and drag to resize selection. Middle click to set highlighted position."); } } if (!editable) { cursor = Qt::ArrowCursor; } } else { // there used to be guides... :'''( } useCursor(cursor); if (currentStrategy() == 0) { emit statusTextChanged(statusText); } } void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter) { KoInteractionTool::paint(painter, converter); - if (currentStrategy() == 0 && koSelection()->count() > 0) { + if (currentStrategy() == 0 && koSelection() && koSelection()->count() > 0) { SelectionDecorator decorator(m_mouseWasInsideHandles ? m_lastHandle : KoFlake::NoHandle, true, true); decorator.setSelection(koSelection()); decorator.setHandleRadius(handleRadius()); decorator.setHotPosition(m_hotPosition); decorator.paint(painter, converter); } painter.save(); KoShape::applyConversion(painter, converter); canvas()->snapGuide()->paint(painter, converter); painter.restore(); } void DefaultTool::mousePressEvent(KoPointerEvent *event) { KoInteractionTool::mousePressEvent(event); updateCursor(); } void DefaultTool::mouseMoveEvent(KoPointerEvent *event) { KoInteractionTool::mouseMoveEvent(event); - if (currentStrategy() == 0 && koSelection()->count() > 0) { + if (currentStrategy() == 0 && koSelection() && koSelection()->count() > 0) { QRectF bound = handlesSize(); if (bound.contains(event->point)) { bool inside; KoFlake::SelectionHandle newDirection = handleAt(event->point, &inside); if (inside != m_mouseWasInsideHandles || m_lastHandle != newDirection) { m_lastHandle = newDirection; m_mouseWasInsideHandles = inside; //repaintDecorations(); } } else { /*if (m_lastHandle != KoFlake::NoHandle) repaintDecorations(); */ m_lastHandle = KoFlake::NoHandle; m_mouseWasInsideHandles = false; // there used to be guides... :'''( } } else { // there used to be guides... :'''( } updateCursor(); } QRectF DefaultTool::handlesSize() { QRectF bound = koSelection()->boundingRect(); // expansion Border if (!canvas() || !canvas()->viewConverter()) { return bound; } QPointF border = canvas()->viewConverter()->viewToDocument(QPointF(HANDLE_DISTANCE, HANDLE_DISTANCE)); bound.adjust(-border.x(), -border.y(), border.x(), border.y()); return bound; } void DefaultTool::mouseReleaseEvent(KoPointerEvent *event) { KoInteractionTool::mouseReleaseEvent(event); updateCursor(); } void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event) { QList shapes; Q_FOREACH (KoShape *shape, koSelection()->selectedShapes()) { if (shape->boundingRect().contains(event->point) && // first 'cheap' check shape->outline().contains(event->point)) { // this is more expensive but weeds out the almost hits shapes.append(shape); } } if (shapes.count() == 0) { // nothing in the selection was clicked on. KoShape *shape = canvas()->shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop); if (shape) { shapes.append(shape); } // there used to be guides... :'''( } QList shapes2; foreach (KoShape *shape, shapes) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { shapes2.append(shape); } else { foreach (KoShape *delegatedShape, delegates) { shapes2.append(delegatedShape); } } } KoToolManager::instance()->switchToolRequested( KoToolManager::instance()->preferredToolForSelection(shapes2)); } bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers) { qreal x = 0.0, y = 0.0; if (direction == Qt::Key_Left) { x = -5; } else if (direction == Qt::Key_Right) { x = 5; } else if (direction == Qt::Key_Up) { y = -5; } else if (direction == Qt::Key_Down) { y = 5; } if (x != 0.0 || y != 0.0) { // actually move if ((modifiers & Qt::ShiftModifier) != 0) { x *= 10; y *= 10; } else if ((modifiers & Qt::AltModifier) != 0) { // more precise x /= 5; y /= 5; } QList prevPos; QList newPos; QList shapes; Q_FOREACH (KoShape *shape, koSelection()->selectedShapes(KoFlake::TopLevelSelection)) { if (shape->isGeometryProtected()) { continue; } shapes.append(shape); QPointF p = shape->position(); prevPos.append(p); p.setX(p.x() + x); p.setY(p.y() + y); newPos.append(p); } if (shapes.count() > 0) { // use a timeout to make sure we don't reuse a command possibly deleted by the commandHistory if (m_lastUsedMoveCommand.msecsTo(QTime::currentTime()) > 5000) { m_moveCommand = 0; } if (m_moveCommand) { // alter previous instead of creating new one. m_moveCommand->setNewPositions(newPos); m_moveCommand->redo(); } else { m_moveCommand = new KoShapeMoveCommand(shapes, prevPos, newPos); canvas()->addCommand(m_moveCommand); } m_lastUsedMoveCommand = QTime::currentTime(); return true; } } return false; } void DefaultTool::keyPressEvent(QKeyEvent *event) { KoInteractionTool::keyPressEvent(event); if (currentStrategy() == 0) { switch (event->key()) { case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: if (moveSelection(event->key(), event->modifiers())) { event->accept(); } break; case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: case Qt::Key_4: case Qt::Key_5: canvas()->resourceManager()->setResource(HotPosition, event->key() - Qt::Key_1); event->accept(); break; default: return; } } } void DefaultTool::customMoveEvent(KoPointerEvent *event) { - if (!koSelection()->count()) { + if (koSelection() && koSelection()->count() <= 0) { event->ignore(); return; } int move = qMax(qAbs(event->x()), qAbs(event->y())); int zoom = qAbs(event->z()); int rotate = qAbs(event->rotationZ()); const int threshold = 2; if (move < threshold && zoom < threshold && rotate < threshold) { if (m_customEventStrategy) { m_customEventStrategy->finishInteraction(event->modifiers()); KUndo2Command *command = m_customEventStrategy->createCommand(); if (command) { canvas()->addCommand(command); } delete m_customEventStrategy; m_customEventStrategy = 0; repaintDecorations(); } event->accept(); return; } // check if the z-movement is dominant if (zoom > move && zoom > rotate) { // zoom if (!m_customEventStrategy) { m_customEventStrategy = new ShapeResizeStrategy(this, event->point, KoFlake::TopLeftHandle); } } else if (move > zoom && move > rotate) { // check if x-/y-movement is dominant // move if (!m_customEventStrategy) { m_customEventStrategy = new ShapeMoveStrategy(this, event->point); } } else if (rotate > zoom && rotate > move) { // rotation is dominant // rotate if (!m_customEventStrategy) { m_customEventStrategy = new ShapeRotateStrategy(this, event->point, event->buttons()); } } if (m_customEventStrategy) { m_customEventStrategy->handleCustomEvent(event); } event->accept(); } void DefaultTool::repaintDecorations() { - Q_ASSERT(koSelection()); - if (koSelection()->count() > 0) { + if (koSelection() && koSelection()->count() > 0) { canvas()->updateCanvas(handlesSize()); } } void DefaultTool::copy() const { QList shapes = canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection); if (!shapes.empty()) { KoShapeOdfSaveHelper saveHelper(shapes); KoDrag drag; drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper); drag.addToClipboard(); } } void DefaultTool::deleteSelection() { QList shapes; foreach (KoShape *s, canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection)) { if (s->isGeometryProtected()) { continue; } shapes << s; } if (!shapes.empty()) { canvas()->addCommand(canvas()->shapeController()->removeShapes(shapes)); } } bool DefaultTool::paste() { // we no longer have to do anything as tool Proxy will do it for us return false; } QStringList DefaultTool::supportedPasteMimeTypes() const { QStringList list; list << KoOdf::mimeType(KoOdf::Text); return list; } KoSelection *DefaultTool::koSelection() { Q_ASSERT(canvas()); Q_ASSERT(canvas()->shapeManager()); return canvas()->shapeManager()->selection(); } KoFlake::SelectionHandle DefaultTool::handleAt(const QPointF &point, bool *innerHandleMeaning) { // check for handles in this order; meaning that when handles overlap the one on top is chosen static const KoFlake::SelectionHandle handleOrder[] = { KoFlake::BottomRightHandle, KoFlake::TopLeftHandle, KoFlake::BottomLeftHandle, KoFlake::TopRightHandle, KoFlake::BottomMiddleHandle, KoFlake::RightMiddleHandle, KoFlake::LeftMiddleHandle, KoFlake::TopMiddleHandle, KoFlake::NoHandle }; - if (koSelection()->count() == 0) { + if (koSelection() && koSelection()->count() == 0) { return KoFlake::NoHandle; } recalcSelectionBox(); const KoViewConverter *converter = canvas()->viewConverter(); if (!converter) { return KoFlake::NoHandle; } if (innerHandleMeaning != 0) { QPainterPath path; path.addPolygon(m_selectionOutline); *innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point)); } for (int i = 0; i < KoFlake::NoHandle; ++i) { KoFlake::SelectionHandle handle = handleOrder[i]; QPointF pt = converter->documentToView(point) - converter->documentToView(m_selectionBox[handle]); // if just inside the outline if (qAbs(pt.x()) < HANDLE_DISTANCE && qAbs(pt.y()) < HANDLE_DISTANCE) { if (innerHandleMeaning != 0) { if (qAbs(pt.x()) < 4 && qAbs(pt.y()) < 4) { *innerHandleMeaning = true; } } return handle; } } return KoFlake::NoHandle; } void DefaultTool::recalcSelectionBox() { + if (!koSelection()) { + return; + } + if (koSelection()->count() == 0) { return; } if (koSelection()->count() > 1) { QTransform matrix = koSelection()->absoluteTransformation(0); m_selectionOutline = matrix.map(QPolygonF(QRectF(QPointF(0, 0), koSelection()->size()))); m_angle = 0.0; //koSelection()->rotation(); } else { QTransform matrix = koSelection()->firstSelectedShape()->absoluteTransformation(0); m_selectionOutline = matrix.map(QPolygonF(QRectF(QPointF(0, 0), koSelection()->firstSelectedShape()->size()))); m_angle = 0.0; //koSelection()->firstSelectedShape()->rotation(); } QPolygonF outline = m_selectionOutline; //shorter name in the following :) m_selectionBox[KoFlake::TopMiddleHandle] = (outline.value(0) + outline.value(1)) / 2; m_selectionBox[KoFlake::TopRightHandle] = outline.value(1); m_selectionBox[KoFlake::RightMiddleHandle] = (outline.value(1) + outline.value(2)) / 2; m_selectionBox[KoFlake::BottomRightHandle] = outline.value(2); m_selectionBox[KoFlake::BottomMiddleHandle] = (outline.value(2) + outline.value(3)) / 2; m_selectionBox[KoFlake::BottomLeftHandle] = outline.value(3); m_selectionBox[KoFlake::LeftMiddleHandle] = (outline.value(3) + outline.value(0)) / 2; m_selectionBox[KoFlake::TopLeftHandle] = outline.value(0); if (koSelection()->count() == 1) { #if 0 // TODO detect mirroring KoShape *s = koSelection()->firstSelectedShape(); if (s->scaleX() < 0) { // vertically mirrored: swap left / right qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]); qSwap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]); qSwap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]); } if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]); qSwap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]); qSwap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]); } #endif } } void DefaultTool::activate(ToolActivation, const QSet &) { m_mouseWasInsideHandles = false; m_lastHandle = KoFlake::NoHandle; useCursor(Qt::ArrowCursor); repaintDecorations(); updateActions(); } void DefaultTool::selectionAlignHorizontalLeft() { selectionAlign(KoShapeAlignCommand::HorizontalLeftAlignment); } void DefaultTool::selectionAlignHorizontalCenter() { selectionAlign(KoShapeAlignCommand::HorizontalCenterAlignment); } void DefaultTool::selectionAlignHorizontalRight() { selectionAlign(KoShapeAlignCommand::HorizontalRightAlignment); } void DefaultTool::selectionAlignVerticalTop() { selectionAlign(KoShapeAlignCommand::VerticalTopAlignment); } void DefaultTool::selectionAlignVerticalCenter() { selectionAlign(KoShapeAlignCommand::VerticalCenterAlignment); } void DefaultTool::selectionAlignVerticalBottom() { selectionAlign(KoShapeAlignCommand::VerticalBottomAlignment); } void DefaultTool::selectionGroup() { KoSelection *selection = koSelection(); if (!selection) { return; } QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); QList groupedShapes; // only group shapes with an unselected parent foreach (KoShape *shape, selectedShapes) { if (!selectedShapes.contains(shape->parent()) && shape->isEditable()) { groupedShapes << shape; } } KoShapeGroup *group = new KoShapeGroup(); // TODO what if only one shape is left? KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes")); canvas()->shapeController()->addShapeDirect(group, cmd); KoShapeGroupCommand::createCommand(group, groupedShapes, cmd); canvas()->addCommand(cmd); // update selection so we can ungroup immediately again selection->deselectAll(); selection->select(group); } void DefaultTool::selectionUngroup() { KoSelection *selection = canvas()->shapeManager()->selection(); if (!selection) { return; } QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); QList containerSet; // only ungroup shape groups with an unselected parent foreach (KoShape *shape, selectedShapes) { if (!selectedShapes.contains(shape->parent()) && shape->isEditable()) { containerSet << shape; } } KUndo2Command *cmd = 0; // add a ungroup command for each found shape container to the macro command Q_FOREACH (KoShape *shape, containerSet) { KoShapeGroup *group = dynamic_cast(shape); if (group) { cmd = cmd ? cmd : new KUndo2Command(kundo2_i18n("Ungroup shapes")); new KoShapeUngroupCommand(group, group->shapes(), group->parent() ? QList() : canvas()->shapeManager()->topLevelShapes(), cmd); canvas()->shapeController()->removeShape(group, cmd); } } if (cmd) { canvas()->addCommand(cmd); } } void DefaultTool::selectionAlign(KoShapeAlignCommand::Align align) { KoSelection *selection = canvas()->shapeManager()->selection(); if (!selection) { return; } QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); if (selectedShapes.count() < 1) { return; } QList editableShapes = filterEditableShapes(selectedShapes); // TODO add an option to the widget so that one can align to the page // with multiple selected shapes too QRectF bb; // single selected shape is automatically aligned to document rect if (editableShapes.count() == 1) { if (!canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize)) { return; } bb = QRectF(QPointF(0, 0), canvas()->resourceManager()->sizeResource(KoCanvasResourceManager::PageSize)); } else { Q_FOREACH (KoShape *shape, editableShapes) { bb |= shape->boundingRect(); } } KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb); canvas()->addCommand(cmd); selection->updateSizeAndPosition(); } void DefaultTool::selectionBringToFront() { selectionReorder(KoShapeReorderCommand::BringToFront); } void DefaultTool::selectionMoveUp() { selectionReorder(KoShapeReorderCommand::RaiseShape); } void DefaultTool::selectionMoveDown() { selectionReorder(KoShapeReorderCommand::LowerShape); } void DefaultTool::selectionSendToBack() { selectionReorder(KoShapeReorderCommand::SendToBack); } void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order) { KoSelection *selection = canvas()->shapeManager()->selection(); if (!selection) { return; } QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); if (selectedShapes.count() < 1) { return; } QList editableShapes = filterEditableShapes(selectedShapes); if (editableShapes.count() < 1) { return; } KUndo2Command *cmd = KoShapeReorderCommand::createCommand(editableShapes, canvas()->shapeManager(), order); if (cmd) { canvas()->addCommand(cmd); } } QList > DefaultTool::createOptionWidgets() { QList > widgets; DefaultToolArrangeWidget *defaultArrange = new DefaultToolArrangeWidget(this); defaultArrange->setWindowTitle(i18n("Arrange")); widgets.append(defaultArrange); DefaultToolWidget *defaultTool = new DefaultToolWidget(this); defaultTool->setWindowTitle(i18n("Geometry")); widgets.append(defaultTool); KoStrokeConfigWidget *strokeWidget = new KoStrokeConfigWidget(0); strokeWidget->setWindowTitle(i18n("Line")); strokeWidget->setCanvas(canvas()); widgets.append(strokeWidget); KoFillConfigWidget *fillWidget = new KoFillConfigWidget(0); fillWidget->setWindowTitle(i18n("Fill")); fillWidget->setCanvas(canvas()); widgets.append(fillWidget); KoShadowConfigWidget *shadowWidget = new KoShadowConfigWidget(0); shadowWidget->setWindowTitle(i18n("Shadow")); shadowWidget->setCanvas(canvas()); widgets.append(shadowWidget); return widgets; } void DefaultTool::canvasResourceChanged(int key, const QVariant &res) { if (key == HotPosition) { m_hotPosition = static_cast(res.toInt()); repaintDecorations(); } } KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event) { // reset the move by keys when a new strategy is created otherwise we might change the // command after a new command was added. This happend when you where faster than the timer. m_moveCommand = 0; KoShapeManager *shapeManager = canvas()->shapeManager(); KoSelection *select = shapeManager->selection(); bool insideSelection; KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection); bool editableShape = editableShapesCount(select->selectedShapes()); if (event->buttons() & Qt::MidButton) { // change the hot selection position when middle clicking on a handle KoFlake::Position newHotPosition = m_hotPosition; switch (handle) { case KoFlake::TopLeftHandle: newHotPosition = KoFlake::TopLeftCorner; break; case KoFlake::TopRightHandle: newHotPosition = KoFlake::TopRightCorner; break; case KoFlake::BottomLeftHandle: newHotPosition = KoFlake::BottomLeftCorner; break; case KoFlake::BottomRightHandle: newHotPosition = KoFlake::BottomRightCorner; break; default: { // check if we had hit the center point const KoViewConverter *converter = canvas()->viewConverter(); QPointF pt = converter->documentToView(event->point - select->absolutePosition()); if (qAbs(pt.x()) < HANDLE_DISTANCE && qAbs(pt.y()) < HANDLE_DISTANCE) { newHotPosition = KoFlake::CenteredPosition; } break; } } if (m_hotPosition != newHotPosition) { canvas()->resourceManager()->setResource(HotPosition, newHotPosition); } return 0; } bool selectMultiple = event->modifiers() & Qt::ControlModifier; bool selectNextInStack = event->modifiers() & Qt::ShiftModifier; if (editableShape) { // manipulation of selected shapes goes first if (handle != KoFlake::NoHandle) { if (event->buttons() == Qt::LeftButton) { // resizing or shearing only with left mouse button if (insideSelection) { return new ShapeResizeStrategy(this, event->point, handle); } if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle || handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) { return new ShapeShearStrategy(this, event->point, handle); } } // rotating is allowed for rigth mouse button too if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle || handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) { return new ShapeRotateStrategy(this, event->point, event->buttons()); } } if (!(selectMultiple || selectNextInStack) && event->buttons() == Qt::LeftButton) { const QPainterPath outlinePath = select->transformation().map(select->outline()); if (outlinePath.contains(event->point) || outlinePath.intersects(handlePaintRect(event->point))) { return new ShapeMoveStrategy(this, event->point); } } } if ((event->buttons() & Qt::LeftButton) == 0) { return 0; // Nothing to do for middle/right mouse button } KoShape *shape = shapeManager->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop); if (!shape && handle == KoFlake::NoHandle) { if (!selectMultiple) { repaintDecorations(); select->deselectAll(); } return new KoShapeRubberSelectStrategy(this, event->point); } if (select->isSelected(shape)) { if (selectMultiple) { repaintDecorations(); select->deselect(shape); } } else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected repaintDecorations(); if (!selectMultiple) { shapeManager->selection()->deselectAll(); } select->select(shape, selectNextInStack ? false : true); repaintDecorations(); // tablet selection isn't precise and may lead to a move, preventing that if (event->isTabletEvent()) { return new NopInteractionStrategy(this); } return new ShapeMoveStrategy(this, event->point); } return 0; } void DefaultTool::updateActions() { KoSelection *selection(koSelection()); if (!selection) { action("object_order_front")->setEnabled(false); action("object_order_raise")->setEnabled(false); action("object_order_lower")->setEnabled(false); action("object_order_back")->setEnabled(false); action("object_align_horizontal_left")->setEnabled(false); action("object_align_horizontal_center")->setEnabled(false); action("object_align_horizontal_right")->setEnabled(false); action("object_align_vertical_top")->setEnabled(false); action("object_align_vertical_center")->setEnabled(false); action("object_align_vertical_bottom")->setEnabled(false); action("object_group")->setEnabled(false); action("object_ungroup")->setEnabled(false); return; } QList editableShapes = filterEditableShapes(selection->selectedShapes(KoFlake::TopLevelSelection)); bool enable = editableShapes.count() > 0; action("object_order_front")->setEnabled(enable); action("object_order_raise")->setEnabled(enable); action("object_order_lower")->setEnabled(enable); action("object_order_back")->setEnabled(enable); enable = (editableShapes.count() > 1) || (enable && canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize)); action("object_align_horizontal_left")->setEnabled(enable); action("object_align_horizontal_center")->setEnabled(enable); action("object_align_horizontal_right")->setEnabled(enable); action("object_align_vertical_top")->setEnabled(enable); action("object_align_vertical_center")->setEnabled(enable); action("object_align_vertical_bottom")->setEnabled(enable); action("object_group")->setEnabled(editableShapes.count() > 1); bool groupShape = false; foreach (KoShape *shape, editableShapes) { if (dynamic_cast(shape)) { groupShape = true; break; } } action("object_ungroup")->setEnabled(groupShape); emit selectionChanged(selection->count()); } KoToolSelection *DefaultTool::selection() { return m_selectionHandler; } QList DefaultTool::filterEditableShapes(const QList &shapes) { QList editableShapes; Q_FOREACH (KoShape *shape, shapes) { if (shape->isEditable()) { editableShapes.append(shape); } } return editableShapes; } uint DefaultTool::editableShapesCount(const QList &shapes) { uint count = 0; Q_FOREACH (KoShape *shape, shapes) { if (shape->isEditable()) { count++; } } return count; } diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp b/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp index 2c6ea52a5f..5c5b0e7b91 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp +++ b/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp @@ -1,277 +1,280 @@ /* This file is part of the KDE project * Copyright (C) 2007 Martin Pfeiffer * Copyright (C) 2007 Jan Hambrecht Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2010 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "DefaultToolWidget.h" #include "DefaultTool.h" #include #include #include #include #include #include #include #include #include #include #include "SelectionDecorator.h" #include "DefaultToolTransformWidget.h" #include #include #include #include #include #include #include #include DefaultToolWidget::DefaultToolWidget(KoInteractionTool *tool, QWidget *parent) : QWidget(parent) , m_tool(tool) , m_blockSignals(false) { setupUi(this); setUnit(m_tool->canvas()->unit()); aspectButton->setKeepAspectRatio(false); updatePosition(); updateSize(); connect(positionSelector, SIGNAL(positionSelected(KoFlake::Position)), this, SLOT(positionSelected(KoFlake::Position))); connect(positionXSpinBox, SIGNAL(editingFinished()), this, SLOT(positionHasChanged())); connect(positionYSpinBox, SIGNAL(editingFinished()), this, SLOT(positionHasChanged())); connect(widthSpinBox, SIGNAL(editingFinished()), this, SLOT(sizeHasChanged())); connect(heightSpinBox, SIGNAL(editingFinished()), this, SLOT(sizeHasChanged())); KoSelection *selection = m_tool->canvas()->shapeManager()->selection(); connect(selection, SIGNAL(selectionChanged()), this, SLOT(updatePosition())); connect(selection, SIGNAL(selectionChanged()), this, SLOT(updateSize())); KoShapeManager *manager = m_tool->canvas()->shapeManager(); connect(manager, SIGNAL(selectionContentChanged()), this, SLOT(updatePosition())); connect(manager, SIGNAL(selectionContentChanged()), this, SLOT(updateSize())); connect(m_tool->canvas()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(resourceChanged(int,QVariant))); connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(aspectButtonToggled(bool))); } void DefaultToolWidget::positionSelected(KoFlake::Position position) { m_tool->canvas()->resourceManager()->setResource(DefaultTool::HotPosition, QVariant(position)); updatePosition(); } void DefaultToolWidget::updatePosition() { QPointF selPosition(0, 0); KoFlake::Position position = positionSelector->position(); KoSelection *selection = m_tool->canvas()->shapeManager()->selection(); - if (selection->count()) { + if (selection && selection->count()) { selPosition = selection->absolutePosition(position); } - positionXSpinBox->setEnabled(selection->count()); - positionYSpinBox->setEnabled(selection->count()); + positionXSpinBox->setEnabled(selection && selection->count()); + positionYSpinBox->setEnabled(selection && selection->count()); if (m_blockSignals) { return; } m_blockSignals = true; positionXSpinBox->changeValue(selPosition.x()); positionYSpinBox->changeValue(selPosition.y()); QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); bool aspectLocked = false; foreach (KoShape *shape, selectedShapes) { aspectLocked = aspectLocked | shape->keepAspectRatio(); } aspectButton->setKeepAspectRatio(aspectLocked); m_blockSignals = false; } void DefaultToolWidget::positionHasChanged() { KoSelection *selection = m_tool->canvas()->shapeManager()->selection(); - if (!selection->count()) { + if (!selection || selection->count() <= 0) { return; } KoFlake::Position position = positionSelector->position(); QPointF newPos(positionXSpinBox->value(), positionYSpinBox->value()); QPointF oldPos = selection->absolutePosition(position); if (oldPos == newPos) { return; } QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); QPointF moveBy = newPos - oldPos; QList oldPositions; QList newPositions; Q_FOREACH (KoShape *shape, selectedShapes) { oldPositions.append(shape->position()); newPositions.append(shape->position() + moveBy); } selection->setPosition(selection->position() + moveBy); m_tool->canvas()->addCommand(new KoShapeMoveCommand(selectedShapes, oldPositions, newPositions)); updatePosition(); } void DefaultToolWidget::updateSize() { QSizeF selSize(0, 0); KoSelection *selection = m_tool->canvas()->shapeManager()->selection(); - uint selectionCount = selection->count(); + uint selectionCount = 0; + if (selection && selection->count()) { + selectionCount = selection->count(); + } if (selectionCount) { selSize = selection->boundingRect().size(); } widthSpinBox->setEnabled(selectionCount); heightSpinBox->setEnabled(selectionCount); if (m_blockSignals) { return; } m_blockSignals = true; widthSpinBox->changeValue(selSize.width()); heightSpinBox->changeValue(selSize.height()); m_blockSignals = false; } void DefaultToolWidget::sizeHasChanged() { if (aspectButton->hasFocus()) { return; } if (m_blockSignals) { return; } QSizeF newSize(widthSpinBox->value(), heightSpinBox->value()); KoSelection *selection = m_tool->canvas()->shapeManager()->selection(); QRectF rect = selection->boundingRect(); if (aspectButton->keepAspectRatio()) { qreal aspect = rect.width() / rect.height(); if (rect.width() != newSize.width()) { newSize.setHeight(newSize.width() / aspect); } else if (rect.height() != newSize.height()) { newSize.setWidth(newSize.height() * aspect); } } if (rect.width() != newSize.width() || rect.height() != newSize.height()) { // get the scale/resize center from the position selector QPointF scaleCenter = selection->absolutePosition(positionSelector->position()); QTransform resizeMatrix; resizeMatrix.translate(scaleCenter.x(), scaleCenter.y()); // make sure not to divide by 0 in case the selection is a line and has no width. In this case just scale by 1. resizeMatrix.scale(rect.width() ? newSize.width() / rect.width() : 1, rect.height() ? newSize.height() / rect.height() : 1); resizeMatrix.translate(-scaleCenter.x(), -scaleCenter.y()); QList selectedShapes = selection->selectedShapes(KoFlake::StrippedSelection); QList oldSizes, newSizes; QList oldState; QList newState; Q_FOREACH (KoShape *shape, selectedShapes) { shape->update(); QSizeF oldSize = shape->size(); oldState << shape->transformation(); QTransform shapeMatrix = shape->absoluteTransformation(0); // calculate the matrix we would apply to the local shape matrix // that tells us the effective scale values we have to use for the resizing QTransform localMatrix = shapeMatrix * resizeMatrix * shapeMatrix.inverted(); // save the effective scale values, without any mirroring portion const qreal scaleX = qAbs(localMatrix.m11()); const qreal scaleY = qAbs(localMatrix.m22()); // calculate the scale matrix which is equivalent to our resizing above QTransform scaleMatrix = (QTransform().scale(scaleX, scaleY)); scaleMatrix = shapeMatrix.inverted() * scaleMatrix * shapeMatrix; // calculate the new size of the shape, using the effective scale values oldSizes << oldSize; QSizeF newSize = QSizeF(scaleX * oldSize.width(), scaleY * oldSize.height()); newSizes << newSize; shape->setSize(newSize); // apply the rest of the transformation without the resizing part shape->applyAbsoluteTransformation(scaleMatrix.inverted() * resizeMatrix); newState << shape->transformation(); } m_tool->repaintDecorations(); selection->applyAbsoluteTransformation(resizeMatrix); KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Resize")); new KoShapeSizeCommand(selectedShapes, oldSizes, newSizes, cmd); new KoShapeTransformCommand(selectedShapes, oldState, newState, cmd); m_tool->canvas()->addCommand(cmd); updateSize(); updatePosition(); } } void DefaultToolWidget::setUnit(const KoUnit &unit) { m_blockSignals = true; positionXSpinBox->setUnit(unit); positionYSpinBox->setUnit(unit); widthSpinBox->setUnit(unit); heightSpinBox->setUnit(unit); m_blockSignals = false; updatePosition(); updateSize(); } void DefaultToolWidget::resourceChanged(int key, const QVariant &res) { if (key == KoCanvasResourceManager::Unit) { setUnit(res.value()); } else if (key == DefaultTool::HotPosition) { if (res.toInt() != positionSelector->position()) { positionSelector->setPosition(static_cast(res.toInt())); updatePosition(); } } } void DefaultToolWidget::aspectButtonToggled(bool keepAspect) { if (m_blockSignals) { return; } KoSelection *selection = m_tool->canvas()->shapeManager()->selection(); foreach (KoShape *shape, selection->selectedShapes(KoFlake::TopLevelSelection)) { shape->setKeepAspectRatio(keepAspect); } } diff --git a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp index 42e0a94371..ab23f8e653 100644 --- a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp +++ b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp @@ -1,161 +1,163 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2006-2007 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SelectionDecorator.h" #include #include #include #define HANDLE_DISTANCE 10 KoFlake::Position SelectionDecorator::m_hotPosition = KoFlake::TopLeftCorner; SelectionDecorator::SelectionDecorator(KoFlake::SelectionHandle arrows, bool rotationHandles, bool shearHandles) : m_rotationHandles(rotationHandles) , m_shearHandles(shearHandles) , m_arrows(arrows) , m_handleRadius(3) , m_lineWidth(1) { } void SelectionDecorator::setSelection(KoSelection *selection) { m_selection = selection; } void SelectionDecorator::setHandleRadius(int radius) { m_handleRadius = radius; m_lineWidth = qMax(1, (int)(radius / 2)); } void SelectionDecorator::setHotPosition(KoFlake::Position hotPosition) { m_hotPosition = hotPosition; } KoFlake::Position SelectionDecorator::hotPosition() { return m_hotPosition; } void SelectionDecorator::paint(QPainter &painter, const KoViewConverter &converter) { QRectF handleArea; painter.save(); // save the original painter transformation QTransform painterMatrix = painter.worldTransform(); QPen pen; //Use the #00adf5 color with 50% opacity pen.setColor(QColor(0, 173, 245, 127)); pen.setWidth(m_lineWidth); pen.setJoinStyle(Qt::RoundJoin); painter.setPen(pen); bool editable = false; foreach (KoShape *shape, m_selection->selectedShapes(KoFlake::StrippedSelection)) { // apply the shape transformation on top of the old painter transformation painter.setWorldTransform(shape->absoluteTransformation(&converter) * painterMatrix); // apply the zoom factor KoShape::applyConversion(painter, converter); // draw the shape bounding rect painter.drawRect(QRectF(QPointF(), shape->size())); if (!shape->isGeometryProtected()) { editable = true; } } - if (m_selection->count() > 1) { - // more than one shape selected, so we need to draw the selection bounding rect - painter.setPen(Qt::blue); - // apply the selection transformation on top of the old painter transformation - painter.setWorldTransform(m_selection->absoluteTransformation(&converter) * painterMatrix); - // apply the zoom factor - KoShape::applyConversion(painter, converter); - // draw the selection bounding rect - painter.drawRect(QRectF(QPointF(), m_selection->size())); - // save the selection bounding rect for later drawing the selection handles - handleArea = QRectF(QPointF(), m_selection->size()); - } else if (m_selection->firstSelectedShape()) { - // only one shape selected, so we compose the correct painter matrix - painter.setWorldTransform(m_selection->firstSelectedShape()->absoluteTransformation(&converter) * painterMatrix); - KoShape::applyConversion(painter, converter); - // save the only selected shapes bounding rect for later drawing the handles - handleArea = QRectF(QPointF(), m_selection->firstSelectedShape()->size()); + if (m_selection) { + if (m_selection->count() > 1) { + // more than one shape selected, so we need to draw the selection bounding rect + painter.setPen(Qt::blue); + // apply the selection transformation on top of the old painter transformation + painter.setWorldTransform(m_selection->absoluteTransformation(&converter) * painterMatrix); + // apply the zoom factor + KoShape::applyConversion(painter, converter); + // draw the selection bounding rect + painter.drawRect(QRectF(QPointF(), m_selection->size())); + // save the selection bounding rect for later drawing the selection handles + handleArea = QRectF(QPointF(), m_selection->size()); + } + else if (m_selection->firstSelectedShape()) { + // only one shape selected, so we compose the correct painter matrix + painter.setWorldTransform(m_selection->firstSelectedShape()->absoluteTransformation(&converter) * painterMatrix); + KoShape::applyConversion(painter, converter); + // save the only selected shapes bounding rect for later drawing the handles + handleArea = QRectF(QPointF(), m_selection->firstSelectedShape()->size()); + } } - painterMatrix = painter.worldTransform(); painter.restore(); // if we have no editable shape selected there is no need drawing the selection handles if (!editable) { return; } painter.save(); painter.setTransform(QTransform()); painter.setRenderHint(QPainter::Antialiasing, false); painter.setPen(pen); painter.setBrush(pen.color()); QPolygonF outline = painterMatrix.map(handleArea); // the 8 move rects QRectF rect(QPointF(0.5, 0.5), QSizeF(2 * m_handleRadius, 2 * m_handleRadius)); rect.moveCenter(outline.value(0)); painter.drawRect(rect); rect.moveCenter(outline.value(1)); painter.drawRect(rect); rect.moveCenter(outline.value(2)); painter.drawRect(rect); rect.moveCenter(outline.value(3)); painter.drawRect(rect); rect.moveCenter((outline.value(0) + outline.value(1)) / 2); painter.drawRect(rect); rect.moveCenter((outline.value(1) + outline.value(2)) / 2); painter.drawRect(rect); rect.moveCenter((outline.value(2) + outline.value(3)) / 2); painter.drawRect(rect); rect.moveCenter((outline.value(3) + outline.value(0)) / 2); painter.drawRect(rect); // draw the hot position painter.setBrush(Qt::red); QPointF pos; switch (m_hotPosition) { case KoFlake::TopLeftCorner: pos = handleArea.topLeft(); break; case KoFlake::TopRightCorner: pos = handleArea.topRight(); break; case KoFlake::BottomLeftCorner: pos = handleArea.bottomLeft(); break; case KoFlake::BottomRightCorner: pos = handleArea.bottomRight(); break; case KoFlake::CenteredPosition: pos = handleArea.center(); break; } rect.moveCenter(painterMatrix.map(pos)); painter.drawRect(rect); painter.restore(); } diff --git a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h index 461480a827..910fb89b61 100644 --- a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h +++ b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h @@ -1,80 +1,82 @@ /* This file is part of the KDE project Copyright (C) 2006 Thorsten Zachmann Copyright (C) 2006-2007 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SELECTIONDECORATOR_H #define SELECTIONDECORATOR_H #include #include + #include +#include class KoSelection; /** * The SelectionDecorator is used to paint extra user-interface items on top of a selection. */ class SelectionDecorator { public: /** * Constructor. * @param arrows the direction that needs highlighting. (currently unused) * @param rotationHandles if true; the rotation handles will be drawn * @param shearHandles if true; the shearhandles will be drawn */ SelectionDecorator(KoFlake::SelectionHandle arrows, bool rotationHandles, bool shearHandles); ~SelectionDecorator() {} /** * paint the decortations. * @param painter the painter to paint to. * @param converter to convert between internal and view coordinates. */ void paint(QPainter &painter, const KoViewConverter &converter); /** * set the selection that is to be painted. * @param selection the current selection. */ void setSelection(KoSelection *selection); /** * set the radius of the selection handles * @param radius the new handle radius */ void setHandleRadius(int radius); /// Sets the hot position to highlight static void setHotPosition(KoFlake::Position hotPosition); /// Returns the hot position static KoFlake::Position hotPosition(); private: bool m_rotationHandles, m_shearHandles; KoFlake::SelectionHandle m_arrows; static KoFlake::Position m_hotPosition; - KoSelection *m_selection; + QPointer m_selection; int m_handleRadius; int m_lineWidth; }; #endif diff --git a/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp b/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp index eb11570a41..2921be94b1 100644 --- a/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp +++ b/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp @@ -1,302 +1,305 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2007,2011 Jan Hambrecht + * Copyright (C) 2017 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ShapeResizeStrategy.h" #include "SelectionDecorator.h" #include #include #include #include #include #include #include #include #include #include #include ShapeResizeStrategy::ShapeResizeStrategy(KoToolBase *tool, const QPointF &clicked, KoFlake::SelectionHandle direction) : KoInteractionStrategy(tool) , m_lastScale(1.0, 1.0) { + Q_ASSERT(tool->canvas()->shapeManager()->selection()); Q_ASSERT(tool->canvas()->shapeManager()->selection()->count() > 0); + QList selectedShapes = tool->canvas()->shapeManager()->selection()->selectedShapes(KoFlake::StrippedSelection); Q_FOREACH (KoShape *shape, selectedShapes) { if (!shape->isEditable()) { continue; } m_selectedShapes << shape; m_startPositions << shape->position(); m_oldTransforms << shape->transformation(); m_transformations << QTransform(); m_startSizes << shape->size(); } m_start = clicked; KoShape *shape = 0; if (tool->canvas()->shapeManager()->selection()->count() > 1) { shape = tool->canvas()->shapeManager()->selection(); } if (tool->canvas()->shapeManager()->selection()->count() == 1) { shape = tool->canvas()->shapeManager()->selection()->firstSelectedShape(); } if (shape) { m_windMatrix = shape->absoluteTransformation(0); m_unwindMatrix = m_windMatrix.inverted(); m_initialSize = shape->size(); m_initialPosition = m_windMatrix.map(QPointF()); switch (direction) { case KoFlake::TopMiddleHandle: m_start = 0.5 * (shape->absolutePosition(KoFlake::TopLeftCorner) + shape->absolutePosition(KoFlake::TopRightCorner)); m_top = true; m_bottom = false; m_left = false; m_right = false; break; case KoFlake::TopRightHandle: m_start = shape->absolutePosition(KoFlake::TopRightCorner); m_top = true; m_bottom = false; m_left = false; m_right = true; break; case KoFlake::RightMiddleHandle: m_start = 0.5 * (shape->absolutePosition(KoFlake::TopRightCorner) + shape->absolutePosition(KoFlake::BottomRightCorner)); m_top = false; m_bottom = false; m_left = false; m_right = true; break; case KoFlake::BottomRightHandle: m_start = shape->absolutePosition(KoFlake::BottomRightCorner); m_top = false; m_bottom = true; m_left = false; m_right = true; break; case KoFlake::BottomMiddleHandle: m_start = 0.5 * (shape->absolutePosition(KoFlake::BottomRightCorner) + shape->absolutePosition(KoFlake::BottomLeftCorner)); m_top = false; m_bottom = true; m_left = false; m_right = false; break; case KoFlake::BottomLeftHandle: m_start = shape->absolutePosition(KoFlake::BottomLeftCorner); m_top = false; m_bottom = true; m_left = true; m_right = false; break; case KoFlake::LeftMiddleHandle: m_start = 0.5 * (shape->absolutePosition(KoFlake::BottomLeftCorner) + shape->absolutePosition(KoFlake::TopLeftCorner)); m_top = false; m_bottom = false; m_left = true; m_right = false; break; case KoFlake::TopLeftHandle: m_start = shape->absolutePosition(KoFlake::TopLeftCorner); m_top = true; m_bottom = false; m_left = true; m_right = false; break; default: Q_ASSERT(0); // illegal 'corner' } } tool->setStatusText(i18n("Press CTRL to resize from center.")); } void ShapeResizeStrategy::handleMouseMove(const QPointF &point, Qt::KeyboardModifiers modifiers) { tool()->canvas()->updateCanvas(tool()->canvas()->snapGuide()->boundingRect()); QPointF newPos = tool()->canvas()->snapGuide()->snap(point, modifiers); tool()->canvas()->updateCanvas(tool()->canvas()->snapGuide()->boundingRect()); bool keepAspect = modifiers & Qt::ShiftModifier; Q_FOREACH (KoShape *shape, m_selectedShapes) { keepAspect = keepAspect || shape->keepAspectRatio(); } qreal startWidth = m_initialSize.width(); if (startWidth < std::numeric_limits::epsilon()) { startWidth = std::numeric_limits::epsilon(); } qreal startHeight = m_initialSize.height(); if (startHeight < std::numeric_limits::epsilon()) { startHeight = std::numeric_limits::epsilon(); } QPointF distance = m_unwindMatrix.map(newPos) - m_unwindMatrix.map(m_start); // guard against resizing zero width shapes, which would result in huge zoom factors if (m_initialSize.width() < std::numeric_limits::epsilon()) { distance.rx() = 0.0; } // guard against resizing zero height shapes, which would result in huge zoom factors if (m_initialSize.height() < std::numeric_limits::epsilon()) { distance.ry() = 0.0; } const bool scaleFromCenter = modifiers & Qt::ControlModifier; if (scaleFromCenter) { distance *= 2.0; } qreal newWidth = startWidth; qreal newHeight = startHeight; if (m_left) { newWidth = startWidth - distance.x(); } else if (m_right) { newWidth = startWidth + distance.x(); } if (m_top) { newHeight = startHeight - distance.y(); } else if (m_bottom) { newHeight = startHeight + distance.y(); } /** * Do not let a shape be less than 1px in size in current view * coordinates. If the user wants it to be smaller, he can just * zoom-in a bit. */ QSizeF minViewSize(1.0, 1.0); QSizeF minDocSize = tool()->canvas()->viewConverter()->viewToDocument(minViewSize); if (qAbs(newWidth) < minDocSize.width()) { int sign = newWidth >= 0.0 ? 1 : -1; // zero -> '1' newWidth = sign * minDocSize.width(); } if (qAbs(newHeight) < minDocSize.height()) { int sign = newHeight >= 0.0 ? 1 : -1; // zero -> '1' newHeight = sign * minDocSize.height(); } qreal zoomX = newWidth / startWidth; qreal zoomY = newHeight / startHeight; if (keepAspect) { const bool cornerUsed = ((m_bottom ? 1 : 0) + (m_top ? 1 : 0) + (m_left ? 1 : 0) + (m_right ? 1 : 0)) == 2; if ((cornerUsed && startWidth < startHeight) || m_left || m_right) { zoomY = zoomX; } else { zoomX = zoomY; } } QPointF move; if (scaleFromCenter) { move = QPointF(startWidth / 2.0, startHeight / 2.0); } else { move = QPointF(m_left ? startWidth : 0, m_top ? startHeight : 0); } resizeBy(move, zoomX, zoomY); } void ShapeResizeStrategy::handleCustomEvent(KoPointerEvent *event) { QPointF center = 0.5 * QPointF(m_initialSize.width(), m_initialSize.height()); qreal zoom = pow(1.01, -0.1 * event->z()); m_lastScale *= zoom; resizeBy(center, m_lastScale.x(), m_lastScale.y()); } void ShapeResizeStrategy::resizeBy(const QPointF ¢er, qreal zoomX, qreal zoomY) { QTransform matrix; matrix.translate(center.x(), center.y()); // translate to matrix.scale(zoomX, zoomY); matrix.translate(-center.x(), -center.y()); // and back // that is the transformation we want to apply to the shapes matrix = m_unwindMatrix * matrix * m_windMatrix; // the resizing transformation without the mirroring part QTransform resizeMatrix; resizeMatrix.translate(center.x(), center.y()); // translate to resizeMatrix.scale(qAbs(zoomX), qAbs(zoomY)); resizeMatrix.translate(-center.x(), -center.y()); // and back // the mirroring part of the resizing transformation QTransform mirrorMatrix; mirrorMatrix.translate(center.x(), center.y()); // translate to mirrorMatrix.scale(zoomX < 0 ? -1 : 1, zoomY < 0 ? -1 : 1); mirrorMatrix.translate(-center.x(), -center.y()); // and back int i = 0; Q_FOREACH (KoShape *shape, m_selectedShapes) { shape->update(); // this uses resize for the zooming part shape->applyAbsoluteTransformation(m_unwindMatrix); /* normally we would just apply the resizeMatrix now and be done with it, but we want to resize instead of scale, so we have to separate the scaling part of that transformation which can then be used to resize */ // undo the last resize transformation shape->applyAbsoluteTransformation(m_transformations[i].inverted()); // save the shapes transformation matrix QTransform shapeMatrix = shape->absoluteTransformation(0); // calculate the matrix we would apply to the local shape matrix // that tells us the effective scale values we have to use for the resizing QTransform localMatrix = shapeMatrix * resizeMatrix * shapeMatrix.inverted(); // save the effective scale values qreal scaleX = localMatrix.m11(); qreal scaleY = localMatrix.m22(); // calculate the scale matrix which is equivalent to our resizing above QTransform scaleMatrix = (QTransform().scale(scaleX, scaleY)); scaleMatrix = shapeMatrix.inverted() * scaleMatrix * shapeMatrix; // calculate the new size of the shape, using the effective scale values QSizeF size(scaleX * m_startSizes[i].width(), scaleY * m_startSizes[i].height()); // apply the transformation shape->setSize(size); // apply the rest of the transformation without the resizing part shape->applyAbsoluteTransformation(scaleMatrix.inverted() * resizeMatrix); shape->applyAbsoluteTransformation(mirrorMatrix); // and remember the applied transformation later for later undoing m_transformations[i] = shapeMatrix.inverted() * shape->absoluteTransformation(0); shape->applyAbsoluteTransformation(m_windMatrix); shape->update(); i++; } tool()->canvas()->shapeManager()->selection()->applyAbsoluteTransformation(matrix * m_scaleMatrix.inverted()); m_scaleMatrix = matrix; } KUndo2Command *ShapeResizeStrategy::createCommand() { tool()->canvas()->snapGuide()->reset(); QList newSizes; QList transformations; const int shapeCount = m_selectedShapes.count(); for (int i = 0; i < shapeCount; ++i) { newSizes << m_selectedShapes[i]->size(); transformations << m_selectedShapes[i]->transformation(); } KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Resize")); new KoShapeSizeCommand(m_selectedShapes, m_startSizes, newSizes, cmd); new KoShapeTransformCommand(m_selectedShapes, m_oldTransforms, transformations, cmd); return cmd; } void ShapeResizeStrategy::finishInteraction(Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); tool()->canvas()->updateCanvas(tool()->canvas()->snapGuide()->boundingRect()); } void ShapeResizeStrategy::paint(QPainter &painter, const KoViewConverter &converter) { SelectionDecorator decorator(KoFlake::NoHandle, false, false); decorator.setSelection(tool()->canvas()->shapeManager()->selection()); decorator.setHandleRadius(handleRadius()); decorator.paint(painter, converter); } diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp index f3a86c5067..45f783d41b 100644 --- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp +++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp @@ -1,512 +1,513 @@ /* This file is part of the KDE project * Copyright (C) 2008 Fela Winkelmolen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KarbonCalligraphyTool.h" #include "KarbonCalligraphicShape.h" #include "KarbonCalligraphyOptionWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef M_PI const qreal M_PI = 3.1415927; using std::pow; using std::sqrt; KarbonCalligraphyTool::KarbonCalligraphyTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_shape(0) , m_angle(0) , m_selectedPath(0) , m_isDrawing(false) , m_speed(0, 0) , m_lastShape(0) { connect(canvas->shapeManager(), SIGNAL(selectionChanged()), SLOT(updateSelectedPath())); updateSelectedPath(); } KarbonCalligraphyTool::~KarbonCalligraphyTool() { } void KarbonCalligraphyTool::paint(QPainter &painter, const KoViewConverter &converter) { if (m_selectedPath) { painter.save(); painter.setRenderHints(QPainter::Antialiasing, false); painter.setPen(Qt::red); // TODO make configurable QRectF rect = m_selectedPath->boundingRect(); QPointF p1 = converter.documentToView(rect.topLeft()); QPointF p2 = converter.documentToView(rect.bottomRight()); painter.drawRect(QRectF(p1, p2)); painter.restore(); } if (!m_shape) { return; } painter.save(); painter.setTransform(m_shape->absoluteTransformation(&converter) * painter.transform()); KoShapePaintingContext paintContext; //FIXME m_shape->paint(painter, converter, paintContext); painter.restore(); } void KarbonCalligraphyTool::mousePressEvent(KoPointerEvent *event) { if (m_isDrawing) { return; } m_lastPoint = event->point; m_speed = QPointF(0, 0); m_isDrawing = true; m_pointCount = 0; m_shape = new KarbonCalligraphicShape(m_caps); m_shape->setBackground(QSharedPointer(new KoColorBackground(canvas()->resourceManager()->foregroundColor().toQColor()))); //addPoint( event ); } void KarbonCalligraphyTool::mouseMoveEvent(KoPointerEvent *event) { if (!m_isDrawing) { return; } addPoint(event); } void KarbonCalligraphyTool::mouseReleaseEvent(KoPointerEvent *event) { if (!m_isDrawing) { return; } if (m_pointCount == 0) { // handle click: select shape (if any) if (event->point == m_lastPoint) { KoShapeManager *shapeManager = canvas()->shapeManager(); KoShape *selectedShape = shapeManager->shapeAt(event->point); if (selectedShape != 0) { shapeManager->selection()->deselectAll(); shapeManager->selection()->select(selectedShape); } } delete m_shape; m_shape = 0; m_isDrawing = false; return; } else { m_endOfPath = false; // allow last point being added addPoint(event); // add last point m_isDrawing = false; } m_shape->simplifyGuidePath(); KUndo2Command *cmd = canvas()->shapeController()->addShape(m_shape); if (cmd) { m_lastShape = m_shape; canvas()->addCommand(cmd); canvas()->updateCanvas(m_shape->boundingRect()); } else { // don't leak shape when command could not be created delete m_shape; } m_shape = 0; } void KarbonCalligraphyTool::addPoint(KoPointerEvent *event) { if (m_pointCount == 0) { if (m_usePath && m_selectedPath) { m_selectedPathOutline = m_selectedPath->outline(); } m_pointCount = 1; m_endOfPath = false; m_followPathPosition = 0; m_lastMousePos = event->point; m_lastPoint = calculateNewPoint(event->point, &m_speed); m_deviceSupportsTilt = (event->xTilt() != 0 || event->yTilt() != 0); return; } if (m_endOfPath) { return; } ++m_pointCount; setAngle(event); QPointF newSpeed; QPointF newPoint = calculateNewPoint(event->point, &newSpeed); qreal width = calculateWidth(event->pressure()); qreal angle = calculateAngle(m_speed, newSpeed); // add the previous point m_shape->appendPoint(m_lastPoint, angle, width); m_speed = newSpeed; m_lastPoint = newPoint; canvas()->updateCanvas(m_shape->lastPieceBoundingRect()); if (m_usePath && m_selectedPath) { m_speed = QPointF(0, 0); // following path } } void KarbonCalligraphyTool::setAngle(KoPointerEvent *event) { if (!m_useAngle) { m_angle = (360 - m_customAngle + 90) / 180.0 * M_PI; return; } // setting m_angle to the angle of the device if (event->xTilt() != 0 || event->yTilt() != 0) { m_deviceSupportsTilt = false; } if (m_deviceSupportsTilt) { if (event->xTilt() == 0 && event->yTilt() == 0) { return; // leave as is } qDebug() << "using tilt" << m_angle; if (event->x() == 0) { m_angle = M_PI / 2; return; } // y is inverted in qt painting m_angle = std::atan(static_cast(-event->yTilt() / event->xTilt())) + M_PI / 2; } else { m_angle = event->rotation() + M_PI / 2; qDebug() << "using rotation" << m_angle; } } QPointF KarbonCalligraphyTool::calculateNewPoint(const QPointF &mousePos, QPointF *speed) { if (!m_usePath || !m_selectedPath) { // don't follow path QPointF force = mousePos - m_lastPoint; QPointF dSpeed = force / m_mass; *speed = m_speed * (1.0 - m_drag) + dSpeed; return m_lastPoint + *speed; } QPointF sp = mousePos - m_lastMousePos; m_lastMousePos = mousePos; // follow selected path qreal step = QLineF(QPointF(0, 0), sp).length(); m_followPathPosition += step; qreal t; if (m_followPathPosition >= m_selectedPathOutline.length()) { t = 1.0; m_endOfPath = true; } else { t = m_selectedPathOutline.percentAtLength(m_followPathPosition); } QPointF res = m_selectedPathOutline.pointAtPercent(t) - + m_selectedPath->position(); + + m_selectedPath->position(); *speed = res - m_lastPoint; return res; } qreal KarbonCalligraphyTool::calculateWidth(qreal pressure) { // calculate the modulo of the speed qreal speed = std::sqrt(pow(m_speed.x(), 2) + pow(m_speed.y(), 2)); qreal thinning = m_thinning * (speed + 1) / 10.0; // can be negative if (thinning > 1) { thinning = 1; } if (!m_usePressure) { pressure = 1.0; } qreal strokeWidth = m_strokeWidth * pressure * (1 - thinning); const qreal MINIMUM_STROKE_WIDTH = 1.0; if (strokeWidth < MINIMUM_STROKE_WIDTH) { strokeWidth = MINIMUM_STROKE_WIDTH; } return strokeWidth; } qreal KarbonCalligraphyTool::calculateAngle(const QPointF &oldSpeed, const QPointF &newSpeed) { // calculate the avarage of the speed (sum of the normalized values) qreal oldLength = QLineF(QPointF(0, 0), oldSpeed).length(); qreal newLength = QLineF(QPointF(0, 0), newSpeed).length(); QPointF oldSpeedNorm = !qFuzzyCompare(oldLength + 1, 1) ? - oldSpeed / oldLength : QPointF(0, 0); + oldSpeed / oldLength : QPointF(0, 0); QPointF newSpeedNorm = !qFuzzyCompare(newLength + 1, 1) ? - newSpeed / newLength : QPointF(0, 0); + newSpeed / newLength : QPointF(0, 0); QPointF speed = oldSpeedNorm + newSpeedNorm; // angle solely based on the speed qreal speedAngle = 0; if (speed.x() != 0) { // avoid division by zero speedAngle = std::atan(speed.y() / speed.x()); } else if (speed.y() > 0) { // x == 0 && y != 0 speedAngle = M_PI / 2; } else if (speed.y() < 0) { // x == 0 && y != 0 speedAngle = -M_PI / 2; } if (speed.x() < 0) { speedAngle += M_PI; } // move 90 degrees speedAngle += M_PI / 2; qreal fixedAngle = m_angle; // check if the fixed angle needs to be flipped qreal diff = fixedAngle - speedAngle; while (diff >= M_PI) { // normalize diff between -180 and 180 diff -= 2 * M_PI; } while (diff < -M_PI) { diff += 2 * M_PI; } if (std::abs(diff) > M_PI / 2) { // if absolute value < 90 fixedAngle += M_PI; // += 180 } qreal dAngle = speedAngle - fixedAngle; // normalize dAngle between -90 and +90 while (dAngle >= M_PI / 2) { dAngle -= M_PI; } while (dAngle < -M_PI / 2) { dAngle += M_PI; } qreal angle = fixedAngle + dAngle * (1.0 - m_fixation); return angle; } void KarbonCalligraphyTool::activate(ToolActivation, const QSet &) { useCursor(Qt::CrossCursor); m_lastShape = 0; } void KarbonCalligraphyTool::deactivate() { if (m_lastShape && canvas()->shapeManager()->shapes().contains(m_lastShape)) { KoSelection *selection = canvas()->shapeManager()->selection(); selection->deselectAll(); selection->select(m_lastShape); } } QList > KarbonCalligraphyTool::createOptionWidgets() { // if the widget don't exists yet create it QList > widgets; KoFillConfigWidget *fillWidget = new KoFillConfigWidget(0); fillWidget->setWindowTitle(i18n("Fill")); fillWidget->setCanvas(canvas()); widgets.append(fillWidget); KarbonCalligraphyOptionWidget *widget = new KarbonCalligraphyOptionWidget; connect(widget, SIGNAL(usePathChanged(bool)), this, SLOT(setUsePath(bool))); connect(widget, SIGNAL(usePressureChanged(bool)), this, SLOT(setUsePressure(bool))); connect(widget, SIGNAL(useAngleChanged(bool)), this, SLOT(setUseAngle(bool))); connect(widget, SIGNAL(widthChanged(double)), this, SLOT(setStrokeWidth(double))); connect(widget, SIGNAL(thinningChanged(double)), this, SLOT(setThinning(double))); connect(widget, SIGNAL(angleChanged(int)), this, SLOT(setAngle(int))); connect(widget, SIGNAL(fixationChanged(double)), this, SLOT(setFixation(double))); connect(widget, SIGNAL(capsChanged(double)), this, SLOT(setCaps(double))); connect(widget, SIGNAL(massChanged(double)), this, SLOT(setMass(double))); connect(widget, SIGNAL(dragChanged(double)), this, SLOT(setDrag(double))); connect(this, SIGNAL(pathSelectedChanged(bool)), widget, SLOT(setUsePathEnabled(bool))); // add shortcuts QAction *action = new QAction(i18n("Calligraphy: increase width"), this); action->setShortcut(Qt::Key_Right); connect(action, SIGNAL(triggered()), widget, SLOT(increaseWidth())); addAction("calligraphy_increase_width", action); action = new QAction(i18n("Calligraphy: decrease width"), this); action->setShortcut(Qt::Key_Left); connect(action, SIGNAL(triggered()), widget, SLOT(decreaseWidth())); addAction("calligraphy_decrease_width", action); action = new QAction(i18n("Calligraphy: increase angle"), this); action->setShortcut(Qt::Key_Up); connect(action, SIGNAL(triggered()), widget, SLOT(increaseAngle())); addAction("calligraphy_increase_angle", action); action = new QAction(i18n("Calligraphy: decrease angle"), this); action->setShortcut(Qt::Key_Down); connect(action, SIGNAL(triggered()), widget, SLOT(decreaseAngle())); addAction("calligraphy_decrease_angle", action); // sync all parameters with the loaded profile widget->emitAll(); widget->setObjectName(i18n("Calligraphy")); widget->setWindowTitle(i18n("Calligraphy")); widgets.append(widget); return widgets; } void KarbonCalligraphyTool::setStrokeWidth(double width) { m_strokeWidth = width; } void KarbonCalligraphyTool::setThinning(double thinning) { m_thinning = thinning; } void KarbonCalligraphyTool::setAngle(int angle) { m_customAngle = angle; } void KarbonCalligraphyTool::setFixation(double fixation) { m_fixation = fixation; } void KarbonCalligraphyTool::setMass(double mass) { m_mass = mass * mass + 1; } void KarbonCalligraphyTool::setDrag(double drag) { m_drag = drag; } void KarbonCalligraphyTool::setUsePath(bool usePath) { m_usePath = usePath; } void KarbonCalligraphyTool::setUsePressure(bool usePressure) { m_usePressure = usePressure; } void KarbonCalligraphyTool::setUseAngle(bool useAngle) { m_useAngle = useAngle; } void KarbonCalligraphyTool::setCaps(double caps) { m_caps = caps; } void KarbonCalligraphyTool::updateSelectedPath() { KoPathShape *oldSelectedPath = m_selectedPath; // save old value KoSelection *selection = canvas()->shapeManager()->selection(); + if (selection) { + // null pointer if it the selection isn't a KoPathShape + // or if the selection is empty + m_selectedPath = + dynamic_cast(selection->firstSelectedShape()); + + // or if it's a KoPathShape but with no or more than one subpaths + if (m_selectedPath && m_selectedPath->subpathCount() != 1) { + m_selectedPath = 0; + } - // null pointer if it the selection isn't a KoPathShape - // or if the selection is empty - m_selectedPath = - dynamic_cast(selection->firstSelectedShape()); - - // or if it's a KoPathShape but with no or more than one subpaths - if (m_selectedPath && m_selectedPath->subpathCount() != 1) { - m_selectedPath = 0; - } - - // or if there ora none or more than 1 shapes selected - if (selection->count() != 1) { - m_selectedPath = 0; - } + // or if there ora none or more than 1 shapes selected + if (selection->count() != 1) { + m_selectedPath = 0; + } - // emit signal it there wasn't a selected path and now there is - // or the other way around - if ((m_selectedPath != 0) != (oldSelectedPath != 0)) { - emit pathSelectedChanged(m_selectedPath != 0); + // emit signal it there wasn't a selected path and now there is + // or the other way around + if ((m_selectedPath != 0) != (oldSelectedPath != 0)) { + emit pathSelectedChanged(m_selectedPath != 0); + } } } diff --git a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp index 98b731b447..3f9d0d78a6 100644 --- a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp +++ b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp @@ -1,290 +1,290 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_lazy_brush_options_widget.h" #include "ui_kis_tool_lazy_brush_options_widget.h" #include #include "KisPaletteModel.h" #include "kis_config.h" #include #include "kis_canvas_resource_provider.h" #include "kis_signal_auto_connection.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_image.h" #include "kis_signals_blocker.h" #include "kis_signal_compressor.h" #include "kis_layer_properties_icons.h" struct KisToolLazyBrushOptionsWidget::Private { Private() : transparentColorIndex(-1), baseNodeChangedCompressor(500, KisSignalCompressor::FIRST_ACTIVE) { } Ui_KisToolLazyBrushOptionsWidget *ui; KisPaletteModel *colorModel; KisCanvasResourceProvider *provider; KisSignalAutoConnectionsStore providerSignals; KisSignalAutoConnectionsStore maskSignals; KisColorizeMaskSP activeMask; KoColorSet colorSet; int transparentColorIndex = -1; KisSignalCompressor baseNodeChangedCompressor; }; KisToolLazyBrushOptionsWidget::KisToolLazyBrushOptionsWidget(KisCanvasResourceProvider *provider, QWidget *parent) : QWidget(parent), m_d(new Private) { m_d->ui = new Ui_KisToolLazyBrushOptionsWidget(); m_d->ui->setupUi(this); m_d->colorModel = new KisPaletteModel(this); m_d->ui->colorView->setPaletteModel(m_d->colorModel); m_d->ui->colorView->setCrossedKeyword("transparent"); connect(m_d->ui->colorView, SIGNAL(clicked(QModelIndex)), this, SLOT(entrySelected(QModelIndex))); connect(m_d->ui->btnTransparent, SIGNAL(toggled(bool)), this, SLOT(slotMakeTransparent(bool))); connect(m_d->ui->btnRemove, SIGNAL(clicked()), this, SLOT(slotRemove())); connect(m_d->ui->chkAutoUpdates, SIGNAL(toggled(bool)), m_d->ui->btnUpdate, SLOT(setDisabled(bool))); connect(m_d->ui->btnUpdate, SIGNAL(clicked()), this, SLOT(slotUpdate())); connect(m_d->ui->chkAutoUpdates, SIGNAL(toggled(bool)), this, SLOT(slotSetAutoUpdates(bool))); connect(m_d->ui->chkShowKeyStrokes, SIGNAL(toggled(bool)), this, SLOT(slotSetShowKeyStrokes(bool))); connect(m_d->ui->chkShowOutput, SIGNAL(toggled(bool)), this, SLOT(slotSetShowOutput(bool))); connect(&m_d->baseNodeChangedCompressor, SIGNAL(timeout()), this, SLOT(slotUpdateNodeProperties())); m_d->provider = provider; const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); m_d->colorSet.add(KoColorSetEntry(KoColor(Qt::red, cs), "color1")); m_d->colorSet.add(KoColorSetEntry(KoColor(Qt::green, cs), "color2")); m_d->colorSet.add(KoColorSetEntry(KoColor(Qt::blue, cs), "color3")); m_d->colorModel->setColorSet(&m_d->colorSet); } KisToolLazyBrushOptionsWidget::~KisToolLazyBrushOptionsWidget() { } void KisToolLazyBrushOptionsWidget::showEvent(QShowEvent *event) { QWidget::showEvent(event); m_d->providerSignals.addConnection( m_d->provider, SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(slotCurrentNodeChanged(KisNodeSP))); m_d->providerSignals.addConnection( m_d->provider, SIGNAL(sigFGColorChanged(const KoColor&)), this, SLOT(slotCurrentFgColorChanged(const KoColor&))); slotCurrentNodeChanged(m_d->provider->currentNode()); slotCurrentFgColorChanged(m_d->provider->fgColor()); } void KisToolLazyBrushOptionsWidget::hideEvent(QHideEvent *event) { QWidget::hideEvent(event); m_d->providerSignals.clear(); } void KisToolLazyBrushOptionsWidget::entrySelected(QModelIndex index) { if (!index.isValid()) return; const int i = m_d->colorModel->idFromIndex(index); - if (i >= 0 && i < m_d->colorSet.nColors()) { - KoColorSetEntry entry = m_d->colorSet.getColor(i); + if (i >= 0 && i < (int)m_d->colorSet.nColors()) { + KoColorSetEntry entry = m_d->colorSet.getColorGlobal(i); m_d->provider->setFGColor(entry.color); } const bool transparentChecked = i >= 0 && i == m_d->transparentColorIndex; KisSignalsBlocker b(m_d->ui->btnTransparent); m_d->ui->btnTransparent->setChecked(transparentChecked); } void KisToolLazyBrushOptionsWidget::slotCurrentFgColorChanged(const KoColor &color) { int selectedIndex = -1; - for (int i = 0; i < m_d->colorSet.nColors(); i++) { - KoColorSetEntry entry = m_d->colorSet.getColor(i); + for (quint32 i = 0; i < m_d->colorSet.nColors(); i++) { + KoColorSetEntry entry = m_d->colorSet.getColorGlobal(i); if (entry.color == color) { - selectedIndex = i; + selectedIndex = (int)i; break; } } m_d->ui->btnRemove->setEnabled(selectedIndex >= 0); m_d->ui->btnTransparent->setEnabled(selectedIndex >= 0); if (selectedIndex < 0) { KisSignalsBlocker b(m_d->ui->btnTransparent); m_d->ui->btnTransparent->setChecked(false); } QModelIndex newIndex = m_d->colorModel->indexFromId(selectedIndex); if (newIndex != m_d->ui->colorView->currentIndex()) { m_d->ui->colorView->setCurrentIndex(newIndex); } } void KisToolLazyBrushOptionsWidget::slotColorLabelsChanged() { m_d->colorSet.clear(); m_d->transparentColorIndex = -1; if (m_d->activeMask) { KisColorizeMask::KeyStrokeColors colors = m_d->activeMask->keyStrokesColors(); m_d->transparentColorIndex = colors.transparentIndex; for (int i = 0; i < colors.colors.size(); i++) { const QString name = i == m_d->transparentColorIndex ? "transparent" : ""; m_d->colorSet.add(KoColorSetEntry(colors.colors[i], name)); } } m_d->colorModel->setColorSet(&m_d->colorSet); slotCurrentFgColorChanged(m_d->provider->fgColor()); } void KisToolLazyBrushOptionsWidget::slotUpdateNodeProperties() { KisSignalsBlocker b(m_d->ui->chkAutoUpdates, m_d->ui->btnUpdate, m_d->ui->chkShowKeyStrokes, m_d->ui->chkShowOutput); // not implemented yet! //m_d->ui->chkAutoUpdates->setEnabled(m_d->activeMask); m_d->ui->chkAutoUpdates->setEnabled(false); bool value = false; value = m_d->activeMask && !KisLayerPropertiesIcons::nodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeNeedsUpdate, true).toBool(); m_d->ui->btnUpdate->setEnabled(m_d->activeMask && !m_d->ui->chkAutoUpdates->isChecked()); m_d->ui->btnUpdate->setChecked(value); value = m_d->activeMask && KisLayerPropertiesIcons::nodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(); m_d->ui->chkShowKeyStrokes->setEnabled(m_d->activeMask); m_d->ui->chkShowKeyStrokes->setChecked(value); value = m_d->activeMask && KisLayerPropertiesIcons::nodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(); m_d->ui->chkShowOutput->setEnabled(m_d->activeMask); m_d->ui->chkShowOutput->setChecked(value); } void KisToolLazyBrushOptionsWidget::slotCurrentNodeChanged(KisNodeSP node) { m_d->maskSignals.clear(); KisColorizeMask *mask = dynamic_cast(node.data()); m_d->activeMask = mask; if (m_d->activeMask) { m_d->maskSignals.addConnection( m_d->activeMask, SIGNAL(sigKeyStrokesListChanged()), this, SLOT(slotColorLabelsChanged())); m_d->maskSignals.addConnection( m_d->provider->currentImage(), SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(slotUpdateNodeProperties())); } slotColorLabelsChanged(); slotUpdateNodeProperties(); m_d->ui->colorView->setEnabled(m_d->activeMask); } void KisToolLazyBrushOptionsWidget::slotMakeTransparent(bool value) { KIS_ASSERT_RECOVER_RETURN(m_d->activeMask); QModelIndex index = m_d->ui->colorView->currentIndex(); if (!index.isValid()) return; const int activeIndex = m_d->colorModel->idFromIndex(index); KIS_ASSERT_RECOVER_RETURN(activeIndex >= 0); KisColorizeMask::KeyStrokeColors colors; - for (int i = 0; i < m_d->colorSet.nColors(); i++) { - colors.colors << m_d->colorSet.getColor(i).color; + for (quint32 i = 0; i < m_d->colorSet.nColors(); i++) { + colors.colors << m_d->colorSet.getColorGlobal(i).color; } colors.transparentIndex = value ? activeIndex : -1; m_d->activeMask->setKeyStrokesColors(colors); } void KisToolLazyBrushOptionsWidget::slotRemove() { KIS_ASSERT_RECOVER_RETURN(m_d->activeMask); QModelIndex index = m_d->ui->colorView->currentIndex(); if (!index.isValid()) return; const int activeIndex = m_d->colorModel->idFromIndex(index); KIS_ASSERT_RECOVER_RETURN(activeIndex >= 0); - const KoColor color = m_d->colorSet.getColor(activeIndex).color; + const KoColor color = m_d->colorSet.getColorGlobal((quint32)activeIndex).color; m_d->activeMask->removeKeyStroke(color); } void KisToolLazyBrushOptionsWidget::slotUpdate() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask); KisLayerPropertiesIcons::setNodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeNeedsUpdate, false, m_d->provider->currentImage()); } void KisToolLazyBrushOptionsWidget::slotSetAutoUpdates(bool value) { ENTER_FUNCTION() << ppVar(value); } void KisToolLazyBrushOptionsWidget::slotSetShowKeyStrokes(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask); KisLayerPropertiesIcons::setNodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, value, m_d->provider->currentImage()); } void KisToolLazyBrushOptionsWidget::slotSetShowOutput(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask); KisLayerPropertiesIcons::setNodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeShowColoring, value, m_d->provider->currentImage()); } diff --git a/sdk/tests/filestest.h b/sdk/tests/filestest.h index ebed5e2fec..250a181458 100644 --- a/sdk/tests/filestest.h +++ b/sdk/tests/filestest.h @@ -1,119 +1,121 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef FILESTEST #define FILESTEST #include "testutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace TestUtil { void testFiles(const QString& _dirname, const QStringList& exclusions, const QString &resultSuffix = QString(), int fuzzy = 0) { QDir dirSources(_dirname); QStringList failuresFileInfo; QStringList failuresDocImage; QStringList failuresCompare; Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) { + qDebug() << sourceFileInfo.fileName(); if (exclusions.indexOf(sourceFileInfo.fileName()) > -1) { continue; } if (!sourceFileInfo.isHidden() && !sourceFileInfo.isDir()) { QFileInfo resultFileInfo(QString(FILES_DATA_DIR) + "/results/" + sourceFileInfo.fileName() + resultSuffix + ".png"); if (!resultFileInfo.exists()) { failuresFileInfo << resultFileInfo.fileName(); continue; } KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc); manager.setBatchMode(true); KisImportExportFilter::ConversionStatus status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString()); Q_UNUSED(status); if (!doc->image()) { failuresDocImage << sourceFileInfo.fileName(); continue; } QString id = doc->image()->colorSpace()->id(); if (id != "GRAYA" && id != "GRAYAU16" && id != "RGBA" && id != "RGBA16") { dbgKrita << "Images need conversion"; doc->image()->convertImageColorSpace(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::NoOptimization); } QTemporaryFile tmpFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".png")); tmpFile.open(); doc->setBackupFile(false); doc->setOutputMimeType("image/png"); + doc->setFileBatchMode(true); doc->saveAs(QUrl("file://" + tmpFile.fileName())); QImage resultImage(resultFileInfo.absoluteFilePath()); resultImage = resultImage.convertToFormat(QImage::Format_ARGB32); QImage sourceImage(tmpFile.fileName()); sourceImage = sourceImage.convertToFormat(QImage::Format_ARGB32); tmpFile.close(); QPoint pt; if (!TestUtil::compareQImages(pt, resultImage, sourceImage, fuzzy)) { failuresCompare << sourceFileInfo.fileName() + ": " + QString("Pixel (%1,%2) has different values").arg(pt.x()).arg(pt.y()).toLatin1(); resultImage.save(sourceFileInfo.fileName() + ".png"); continue; } delete doc; } } if (failuresCompare.isEmpty() && failuresDocImage.isEmpty() && failuresFileInfo.isEmpty()) { return; } - dbgKrita << "Comparison failures: " << failuresCompare; - dbgKrita << "No image failures: " << failuresDocImage; - dbgKrita << "No comparison image: " << failuresFileInfo; + qWarning() << "Comparison failures: " << failuresCompare; + qWarning() << "No image failures: " << failuresDocImage; + qWarning() << "No comparison image: " << failuresFileInfo; QFAIL("Failed testing files"); } } #endif