diff --git a/3rdparty/README.md b/3rdparty/README.md index 0aedb97c03..50b81425fb 100644 --- a/3rdparty/README.md +++ b/3rdparty/README.md @@ -1,277 +1,277 @@ # CMake external projects to build krita's dependencies on Linux, Windows or OSX If you need to build Krita's dependencies for the following reasons: * you develop on Windows and aren't using build-tools/windows/build.cmd or KDE's craft * you develop on OSX and aren't using the scripts in krita/packaging/osx or Homebrew * you want to build a generic, distro-agnostic version of Krita for Linux and aren't using the scripts in packaging/linux/appimage * you develop on Linux, but some dependencies aren't available for your distribution and aren't using the scripts in packaging/linux/appimage and you know what you're doing, you can use the following guide to build the dependencies that Krita needs. If you develop on Linux and your distribution has all dependencies available, YOU DO NOT NEED THIS GUIDE AND YOU SHOULD STOP READING NOW Otherwise you risk major confusion. ## Prerequisites Note: on all operating systems the entire procedure is done in a terminal window. 1. git: https://git-scm.com/downloads. Make sure git is in your path 2. CMake 3.3.2 or later: https://cmake.org/download/. Make sure cmake is in your path. * CMake 3.9 does not build Krita properly at the moment, please use 3.8 or 3.10 instead. 3. Make sure you have a compiler: * Linux: gcc, minimum version 4.8 * OSX: clang, you need to install xcode for this - * Windows: mingw-w64 7.3 (by mingw-builds): https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/ + * Windows: mingw-w64 7.3 (by mingw-builds): https://files.kde.org/krita/build/x86_64-7.3.0-release-posix-seh-rt_v5-rev0.7z * For threading, select posix. * For exceptions, select seh (64-bit) or dwarf (32-bit). * Install mingw to something like C:\mingw; the full path must not contain any spaces. * Make sure mingw's bin folder is in your path. It might be a good idea to create a batch file which sets the path and start cmd. * MSVC is *not* supported at the moment. 4. On Windows, you will also need a release of Python 3.8 (*not* 3.7 or 3.9 or any other versions): https://www.python.org. Make sure to have that version of python.exe in your path. This version of Python will be used for two things: to configure Qt and to build the Python scripting module. Make sure that this version of Python comes first in your path. Do not set PYTHONHOME or PYTHONPATH. * Make sure that your Python will have the correct architecture for the version you are trying to build. If building for 32-bit target, you need the 32-bit release of Python. 5. On Windows, if you want to compile Qt with ANGLE support, you will need to install Windows 10 SDK and have the environment variable `WindowsSdkDir` set to it (typically `C:\Program Files (x86)\Windows Kits\10`) ## Setup your environment ## Prepare your directory layout 1. Make a toplevel build directory, say $HOME/dev or c:\dev. We'll refer to this directory as BUILDROOT. You can use a variable for this, on WINDOWS %BUILDROOT%, on OSX and Linux $BUILDROOT. You will have to replace a bare BUILDROOT with $BUILDROOT or %BUILDROOT% whenever you copy and paste a command, depending on your operating system. 2. Checkout krita in BUILDROOT ``` cd BUILDROOT git clone git://anongit.kde.org/krita.git ``` 3. Create the build directory ``` mkdir BUILDROOT/b ``` 4. Create the downloads directory ``` mkdir BUILDROOT/d ``` 5. Create the install directory ``` mkdir BUILDROOT/i ``` ## Prepare the externals build 1. Enter the BUILDROOT/b directory 2. Run cmake: * Linux: ``` export PATH=$BUILDROOT/i/bin:$PATH export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python) cmake ../krita/3rdparty \ -DINSTALL_ROOT=$BUILDROOT/i \ -DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \ -DCMAKE_INSTALL_PREFIX=BUILDROOT/i ``` * OSX: ``` export PATH=$BUILDROOT/i/bin:$PATH export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python) cmake ../krita/3rdparty/ \ -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i \ -DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \ -DINSTALL_ROOT=$BUILDROOT/i ``` * Windows 32-bit / 64-bit: Note that the cmake command needs to point to your BUILDROOT like /dev/d, not c:\dev\d. ``` set PATH=%BUILDROOT%\i\bin\;%BUILDROOT%\i\lib;%PATH% cmake ..\krita\3rdparty -DEXTERNALS_DOWNLOAD_DIR=/dev/d -DINSTALL_ROOT=/dev/i -G "MinGW Makefiles" ``` - If you want to build Qt and some other dependencies with parallel jobs, add `-DSUBMAKE_JOBS=` to this cmake command where is the number of jobs to run (if your PC has 4 CPU cores, you might want to set it to 5). For other jobs, you might need to manually add a -- -j N option, where N is the number of jobs. - If you don't have Windows 10 SDK and don't want to build Qt with ANGLE, add `-DQT_ENABLE_DYNAMIC_OPENGL=OFF` to the CMake command line args. 3. Build the packages: On Windows: ``` cmake --build . --config RelWithDebInfo --target ext_patch cmake --build . --config RelWithDebInfo --target ext_png2ico ``` On OSX and Windows: ``` cmake --build . --config RelWithDebInfo --target ext_gettext cmake --build . --config RelWithDebInfo --target ext_openssl ``` On all operating systems: ``` cmake --build . --config RelWithDebInfo --target ext_qt cmake --build . --config RelWithDebInfo --target ext_zlib cmake --build . --config RelWithDebInfo --target ext_boost Note about boost: check if the headers are installed into i/include/boost, but not into i/include/boost-1.61/boost cmake --build . --config RelWithDebInfo --target ext_eigen3 cmake --build . --config RelWithDebInfo --target ext_exiv2 cmake --build . --config RelWithDebInfo --target ext_fftw3 cmake --build . --config RelWithDebInfo --target ext_ilmbase cmake --build . --config RelWithDebInfo --target ext_jpeg cmake --build . --config RelWithDebInfo --target ext_lcms2 cmake --build . --config RelWithDebInfo --target ext_ocio cmake --build . --config RelWithDebInfo --target ext_openexr ``` OSX Note: You need to first build openexr; that will fail; then you need to set the rpath for the two utilities correctly, then try to build openexr again. ``` install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups ``` On All operating systems ``` cmake --build . --config RelWithDebInfo --target ext_png cmake --build . --config RelWithDebInfo --target ext_tiff cmake --build . --config RelWithDebInfo --target ext_gsl cmake --build . --config RelWithDebInfo --target ext_vc cmake --build . --config RelWithDebInfo --target ext_libraw cmake --build . --config RelWithDebInfo --target ext_giflib cmake --build . --config RelWithDebInfo --target ext_openjpeg cmake --build . --config RelWithDebInfo --target ext_quazip ``` On Linux (if you want to build your own SIP and PyQt instead of the system one) ``` cmake --build . --config RelWithDebInfo --target ext_sip cmake --build . --config RelWithDebInfo --target ext_pyqt ``` On Windows ``` cmake --build . --config RelWithDebInfo --target ext_freetype cmake --build . --config RelWithDebInfo --target ext_poppler ``` On Linux ``` cmake --build . --config RelWithDebInfo --target ext_kcrash ``` On Windows (if you want to include DrMingw for dumping backtrace on crash) ``` cmake --build . --config RelWithDebInfo --target ext_drmingw ``` On Windows (if you want to include Python scripting) ``` cmake --build . --config RelWithDebInfo --target ext_python cmake --build . --config RelWithDebInfo --target ext_sip cmake --build . --config RelWithDebInfo --target ext_pyqt ``` Troubleshooting: if you have problems with 'install' step of ext_sip or ext_pyqt, make sure you install it in single thread only (`mingw32-make -j1 install`). Otherwise, a race condition may happen in the post-install script and metadata generation will be started before actual libraries are installed. On Windows and Linux (if you want to include gmic-qt) ``` cmake --build . --config RelWithDebInfo --target ext_gmic ``` Linux Note: poppler should be buildable on Linux as well with a home-built freetype and fontconfig, but I don't know how to make fontconfig find freetype, and on Linux, fontconfig is needed for poppler. Poppler is needed for PDF import. OSX Note: In order to build fontconfig on macOS, you need to have pkg-config installed. You probably need homebrew for that... See http://macappstore.org/pkg-config/ . archives from: files.kde.org/krita/build/dependencies: On Windows and OSX ``` cmake --build . --config RelWithDebInfo --target ext_kwindowsystem ``` ## Build Krita 1. Make a krita build directory: mkdir BUILDROOT/build 2. Enter the BUILDROOT/build 3. Configure the build: On Windows ``` cmake ..\krita -G "MinGW Makefiles" -DBoost_DEBUG=OFF -DBOOST_INCLUDEDIR=c:\dev\i\include -DBOOST_DEBUG=ON -DBOOST_ROOT=c:\dev\i -DBOOST_LIBRARYDIR=c:\dev\i\lib -DCMAKE_INSTALL_PREFIX=c:\dev\i -DCMAKE_PREFIX_PATH=c:\dev\i -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DHAVE_MEMORY_LEAK_TRACKER=OFF -Wno-dev -DDEFINE_NO_DEPRECATED=1 ``` On Linux ``` cmake ../krita -DCMAKE_INSTALL_PREFIX=BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo # Troubleshooting: if you built your own SIP and CMake fails to find it, please set # the following environment variable to the SIP installation directory: export PYTHONPATH=$BUILDROOT/i/sip/ # If you also have KIO installed in the system, don't forget to disable it by bassing to cmake: # cmake -DCMAKE_DISABLE_FIND_PACKAGE_KF5KIO=true . ``` On OSX ``` cmake ../krita -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DBUNDLE_INSTALL_DIR=$BUILDROOT/i/bin -DCMAKE_BUILD_TYPE=RelWithDebInfo ``` 4. Run the build: On Linux and OSX ``` make make install ``` On Windows (replace 4 with the number of jobs to run in parallel) ``` cmake --build . --target install -- -j4 ``` 6. Run krita: On Linux ``` BUILDROOT/i/bin/krita ``` On Windows ``` BUILDROOT\i\bin\krita.exe ``` On OSX ``` BUILDROOT/i/bin/krita.app/Contents/MacOS/krita ``` ## Packaging a Windows Build If you want to create a stripped down version of Krita to distribute, after building everything just run the packaging/windows/package-complete.cmd script. That script will copy the necessary files into the specified folder and leave out developer related files. After the script runs there will be two new ZIP files that contain a small portable version of Krita and a separate portable debug version. diff --git a/libs/brush/kis_abr_brush_collection.cpp b/libs/brush/kis_abr_brush_collection.cpp index 16c0707ff3..cc21f5ab42 100644 --- a/libs/brush/kis_abr_brush_collection.cpp +++ b/libs/brush/kis_abr_brush_collection.cpp @@ -1,642 +1,641 @@ /* * Copyright (c) 2010 Boudewijn Rempt * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2007 Eric Lamarque * * 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 "kis_abr_brush_collection.h" #include "kis_abr_brush.h" #include #include #include #include #include #include #include #include #include #include -#include -#include +#include +#include #include #include struct AbrInfo { //big endian short version; short subversion; // count of the images (brushes) in the abr file short count; }; /// save the QImages as png files to directory image_tests static QImage convertToQImage(char * buffer, qint32 width, qint32 height) { // create 8-bit indexed image QImage img(width, height, QImage::Format_RGB32); int pos = 0; int value = 0; for (int y = 0; y < height; y++) { QRgb *pixel = reinterpret_cast(img.scanLine(y)); for (int x = 0; x < width; x++, pos++) { value = 255 - buffer[pos]; pixel[x] = qRgb(value, value , value); } } return img; } static qint32 rle_decode(QDataStream & abr, char *buffer, qint32 height) { qint32 n; char ptmp; char ch; int i, j, c; short *cscanline_len; char *data = buffer; // read compressed size foreach scanline cscanline_len = new short[ height ]; for (i = 0; i < height; i++) { // short abr >> cscanline_len[i]; } // unpack each scanline data for (i = 0; i < height; i++) { for (j = 0; j < cscanline_len[i];) { // char if (!abr.device()->getChar(&ptmp)) { break; } n = ptmp; j++; if (n >= 128) // force sign n -= 256; if (n < 0) { // copy the following char -n + 1 times if (n == -128) // it's a nop continue; n = -n + 1; // char if (!abr.device()->getChar(&ch)) { break; } j++; for (c = 0; c < n; c++, data++) { *data = ch; } } else { // read the following n + 1 chars (no compr) for (c = 0; c < n + 1; c++, j++, data++) { // char if (!abr.device()->getChar(data)) { break; } } } } } delete [] cscanline_len; return 0; } static QString abr_v1_brush_name(const QString filename, qint32 id) { QString result = filename; int pos = filename.lastIndexOf('.'); result.remove(pos, 4); QTextStream(&result) << "_" << id; return result; } static bool abr_supported_content(AbrInfo *abr_hdr) { switch (abr_hdr->version) { case 1: case 2: return true; break; case 6: if (abr_hdr->subversion == 1 || abr_hdr->subversion == 2) return true; break; } return false; } static bool abr_reach_8BIM_section(QDataStream & abr, const QString name) { char tag[4]; char tagname[5]; qint32 section_size = 0; int r; // find 8BIMname section while (!abr.atEnd()) { r = abr.readRawData(tag, 4); if (r != 4) { warnKrita << "Error: Cannot read 8BIM tag "; return false; } if (strncmp(tag, "8BIM", 4)) { warnKrita << "Error: Start tag not 8BIM but " << (int)tag[0] << (int)tag[1] << (int)tag[2] << (int)tag[3] << " at position " << abr.device()->pos(); return false; } r = abr.readRawData(tagname, 4); if (r != 4) { warnKrita << "Error: Cannot read 8BIM tag name"; return false; } tagname[4] = '\0'; QString s1 = QString::fromLatin1(tagname, 4); if (!s1.compare(name)) { return true; } // long abr >> section_size; abr.device()->seek(abr.device()->pos() + section_size); } return true; } static qint32 find_sample_count_v6(QDataStream & abr, AbrInfo *abr_info) { qint64 origin; qint32 sample_section_size; qint32 sample_section_end; qint32 samples = 0; qint32 data_start; qint32 brush_size; qint32 brush_end; if (!abr_supported_content(abr_info)) return 0; origin = abr.device()->pos(); if (!abr_reach_8BIM_section(abr, "samp")) { // reset to origin abr.device()->seek(origin); return 0; } // long abr >> sample_section_size; sample_section_end = sample_section_size + abr.device()->pos(); if(sample_section_end < 0 || sample_section_end > abr.device()->size()) return 0; data_start = abr.device()->pos(); while ((!abr.atEnd()) && (abr.device()->pos() < sample_section_end)) { // read long abr >> brush_size; brush_end = brush_size; // complement to 4 while (brush_end % 4 != 0) brush_end++; qint64 newPos = abr.device()->pos() + brush_end; if(newPos > 0 && newPos < abr.device()->size()) { abr.device()->seek(newPos); } else return 0; samples++; } // set stream to samples data abr.device()->seek(data_start); //dbgKrita <<"samples : "<< samples; return samples; } static bool abr_read_content(QDataStream & abr, AbrInfo *abr_hdr) { abr >> abr_hdr->version; abr_hdr->subversion = 0; abr_hdr->count = 0; switch (abr_hdr->version) { case 1: case 2: abr >> abr_hdr->count; break; case 6: abr >> abr_hdr->subversion; abr_hdr->count = find_sample_count_v6(abr, abr_hdr); break; default: // unknown versions break; } // next bytes in abr are samples data return true; } static QString abr_read_ucs2_text(QDataStream & abr) { quint32 name_size; quint32 buf_size; uint i; /* two-bytes characters encoded (UCS-2) * format: * long : size - number of characters in string * data : zero terminated UCS-2 string */ // long abr >> name_size; if (name_size == 0) { return QString(); } //buf_size = name_size * 2; buf_size = name_size; //name_ucs2 = (char*) malloc (buf_size * sizeof (char)); //name_ucs2 = new char[buf_size]; ushort * name_ucs2 = new ushort[buf_size]; for (i = 0; i < buf_size ; i++) { //* char*/ //abr >> name_ucs2[i]; // I will use ushort as that is input to fromUtf16 abr >> name_ucs2[i]; } QString name_utf8 = QString::fromUtf16(name_ucs2, buf_size); delete [] name_ucs2; return name_utf8; } quint32 KisAbrBrushCollection::abr_brush_load_v6(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id) { Q_UNUSED(image_ID); qint32 brush_size = 0; qint32 brush_end = 0; qint32 next_brush = 0; qint32 top, left, bottom, right; top = left = bottom = right = 0; short depth; char compression; qint32 width = 0; qint32 height = 0; qint32 size = 0; qint32 layer_ID = -1; char *buffer; abr >> brush_size; brush_end = brush_size; // complement to 4 while (brush_end % 4 != 0) { brush_end++; } next_brush = abr.device()->pos() + brush_end; // discard key abr.device()->seek(abr.device()->pos() + 37); if (abr_hdr->subversion == 1) // discard short coordinates and unknown short abr.device()->seek(abr.device()->pos() + 10); else // discard unknown bytes abr.device()->seek(abr.device()->pos() + 264); // long abr >> top; abr >> left; abr >> bottom; abr >> right; // short abr >> depth; // char abr.device()->getChar(&compression); width = right - left; height = bottom - top; size = width * (depth >> 3) * height; // remove .abr and add some id, so something like test.abr -> test_12345 QString name = abr_v1_brush_name(filename, id); buffer = (char*)malloc(size); // data decoding if (!compression) { // not compressed - read raw bytes as brush data //fread (buffer, size, 1, abr); abr.readRawData(buffer, size); } else { rle_decode(abr, buffer, height); } if (width < quint16_MAX && height < quint16_MAX) { // filename - filename of the file , e.g. test.abr // name - test_number_of_the_brush, e.g test_1, test_2 KisAbrBrushSP abrBrush; if (m_abrBrushes->contains(name)) { abrBrush = m_abrBrushes.data()->operator[](name); } else { abrBrush = KisAbrBrushSP(new KisAbrBrush(name, this)); abrBrush->setMD5(md5()); } abrBrush->setBrushTipImage(convertToQImage(buffer, width, height)); // XXX: call extra setters on abrBrush for other options of ABR brushes abrBrush->setValid(true); abrBrush->setName(name); m_abrBrushes.data()->operator[](name) = abrBrush; } free(buffer); abr.device()->seek(next_brush); layer_ID = id; return layer_ID; } qint32 KisAbrBrushCollection::abr_brush_load_v12(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id) { Q_UNUSED(image_ID); short brush_type; qint32 brush_size; qint32 next_brush; qint32 top, left, bottom, right; qint16 depth; char compression; QString name; qint32 width, height; qint32 size; qint32 layer_ID = -1; char *buffer; // short abr >> brush_type; // long abr >> brush_size; next_brush = abr.device()->pos() + brush_size; if (brush_type == 1) { // computed brush // FIXME: support it! warnKrita << "WARNING: computed brush unsupported, skipping."; abr.device()->seek(abr.device()->pos() + next_brush); // TODO: test also this one abr.skipRawData(next_brush); } else if (brush_type == 2) { // sampled brush // discard 4 misc bytes and 2 spacing bytes abr.device()->seek(abr.device()->pos() + 6); if (abr_hdr->version == 2) name = abr_read_ucs2_text(abr); if (name.isNull()) { name = abr_v1_brush_name(filename, id); } // discard 1 byte for antialiasing and 4 x short for short bounds abr.device()->seek(abr.device()->pos() + 9); // long abr >> top; abr >> left; abr >> bottom; abr >> right; // short abr >> depth; // char abr.device()->getChar(&compression); width = right - left; height = bottom - top; size = width * (depth >> 3) * height; /* FIXME: support wide brushes */ if (height > 16384) { warnKrita << "WARNING: wide brushes not supported"; abr.device()->seek(next_brush); } else { buffer = (char*)malloc(size); if (!compression) { // not compressed - read raw bytes as brush data abr.readRawData(buffer, size); } else { rle_decode(abr, buffer, height); } KisAbrBrushSP abrBrush; if (m_abrBrushes->contains(name)) { abrBrush = m_abrBrushes.data()->operator[](name); } else { abrBrush = KisAbrBrushSP(new KisAbrBrush(name, this)); abrBrush->setMD5(md5()); } abrBrush->setBrushTipImage(convertToQImage(buffer, width, height)); // XXX: call extra setters on abrBrush for other options of ABR brushes free (buffer); abrBrush->setValid(true); abrBrush->setName(name); m_abrBrushes.data()->operator[](name) = abrBrush; layer_ID = 1; } } else { warnKrita << "Unknown ABR brush type, skipping."; abr.device()->seek(next_brush); } return layer_ID; } qint32 KisAbrBrushCollection::abr_brush_load(QDataStream & abr, AbrInfo *abr_hdr, const QString filename, qint32 image_ID, qint32 id) { qint32 layer_ID = -1; switch (abr_hdr->version) { case 1: Q_FALLTHROUGH(); // fall through, version 1 and 2 are compatible case 2: layer_ID = abr_brush_load_v12(abr, abr_hdr, filename, image_ID, id); break; case 6: layer_ID = abr_brush_load_v6(abr, abr_hdr, filename, image_ID, id); break; } return layer_ID; } KisAbrBrushCollection::KisAbrBrushCollection(const QString& filename) : m_isLoaded(false) , m_lastModified() , m_filename(filename) , m_abrBrushes(new QMap()) { } KisAbrBrushCollection::KisAbrBrushCollection(const KisAbrBrushCollection& rhs) : m_isLoaded(rhs.m_isLoaded) , m_lastModified(rhs.m_lastModified) { m_abrBrushes.reset(new QMap()); for (auto it = rhs.m_abrBrushes->begin(); it != rhs.m_abrBrushes->end(); ++it) { m_abrBrushes->insert(it.key(), KisAbrBrushSP(new KisAbrBrush(*it.value(), this))); } } bool KisAbrBrushCollection::load() { m_isLoaded = true; QFile file(filename()); QFileInfo info(file); m_lastModified = info.lastModified(); // check if the file is open correctly if (!file.open(QIODevice::ReadOnly)) { warnKrita << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KisAbrBrushCollection::loadFromDevice(QIODevice *dev) { AbrInfo abr_hdr; qint32 image_ID; int i; qint32 layer_ID; QByteArray ba = dev->readAll(); - KoHashGenerator *hashGenerator = KoHashGeneratorProvider::instance()->getGenerator("MD5"); - m_md5 = hashGenerator->generateHash(ba); + m_md5 = KoMD5Generator::generateHash(ba); QBuffer buf(&ba); buf.open(QIODevice::ReadOnly); QDataStream abr(&buf); if (!abr_read_content(abr, &abr_hdr)) { warnKrita << "Error: cannot parse ABR file: " << filename(); return false; } if (!abr_supported_content(&abr_hdr)) { warnKrita << "ERROR: unable to decode abr format version " << abr_hdr.version << "(subver " << abr_hdr.subversion << ")"; return false; } if (abr_hdr.count == 0) { errKrita << "ERROR: no sample brush found in " << filename(); return false; } image_ID = 123456; for (i = 0; i < abr_hdr.count; i++) { layer_ID = abr_brush_load(abr, &abr_hdr, QFileInfo(filename()).fileName(), image_ID, i + 1); if (layer_ID == -1) { warnKrita << "Warning: problem loading brush #" << i << " in " << filename(); } } return true; } bool KisAbrBrushCollection::save() { return false; } bool KisAbrBrushCollection::saveToDevice(QIODevice */*dev*/) const { return false; } bool KisAbrBrushCollection::isLoaded() const { return m_isLoaded; } QImage KisAbrBrushCollection::image() const { return QImage(); } void KisAbrBrushCollection::toXML(QDomDocument& d, QDomElement& e) const { Q_UNUSED(d); Q_UNUSED(e); // Do nothing... } QString KisAbrBrushCollection::defaultFileExtension() const { return QString(".abr"); } diff --git a/libs/flake/resources/KoGamutMask.cpp b/libs/flake/resources/KoGamutMask.cpp index bd2f8f4121..e757fe118e 100644 --- a/libs/flake/resources/KoGamutMask.cpp +++ b/libs/flake/resources/KoGamutMask.cpp @@ -1,437 +1,437 @@ /* * Copyright (c) 2018 Anna Medonosova * * 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 "KoGamutMask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include KoGamutMaskShape::KoGamutMaskShape(KoShape* shape) : m_maskShape(shape) , m_shapePaintingContext(KoShapePaintingContext()) { } KoGamutMaskShape::KoGamutMaskShape() {}; KoGamutMaskShape::~KoGamutMaskShape() {}; KoShape* KoGamutMaskShape::koShape() { return m_maskShape; } bool KoGamutMaskShape::coordIsClear(const QPointF& coord) const { bool isClear = m_maskShape->hitTest(coord); return isClear; } void KoGamutMaskShape::paint(QPainter &painter) { painter.save(); painter.setTransform(m_maskShape->absoluteTransformation(), true); m_maskShape->paint(painter, m_shapePaintingContext); painter.restore(); } void KoGamutMaskShape::paintStroke(QPainter &painter) { painter.save(); painter.setTransform(m_maskShape->absoluteTransformation(), true); m_maskShape->paintStroke(painter, m_shapePaintingContext); painter.restore(); } struct KoGamutMask::Private { QString name; QString title; - QString description; QByteArray data; QVector maskShapes; QVector previewShapes; QSizeF maskSize; int rotation {0}; }; KoGamutMask::KoGamutMask(const QString& filename) : KoResource(filename) , d(new Private) { d->maskSize = QSizeF(144.0,144.0); setRotation(0); } KoGamutMask::KoGamutMask() : KoResource(QString()) , d(new Private) { d->maskSize = QSizeF(144.0,144.0); setRotation(0); } KoGamutMask::KoGamutMask(KoGamutMask* rhs) : QObject(0) , KoResource(QString()) , d(new Private) { *this = *rhs; } KoGamutMask::KoGamutMask(const KoGamutMask &rhs) : QObject(0) , KoResource(rhs) , d(new Private) { *this = rhs; } KoGamutMask &KoGamutMask::operator=(const KoGamutMask &rhs) { if (*this != rhs) { setTitle(rhs.title()); setDescription(rhs.description()); d->maskSize = rhs.d->maskSize; QList newShapes; for(KoShape* sh: rhs.koShapes()) { newShapes.append(sh->cloneShape()); } setMaskShapes(newShapes); } return *this; } KoResourceSP KoGamutMask::clone() const { return KoResourceSP(new KoGamutMask(*this)); } KoGamutMask::~KoGamutMask() { delete d; } bool KoGamutMask::coordIsClear(const QPointF& coord, bool preview) { QVector* shapeVector; if (preview && !d->previewShapes.isEmpty()) { shapeVector = &d->previewShapes; } else { shapeVector = &d->maskShapes; } for(KoGamutMaskShape* shape: *shapeVector) { if (shape->coordIsClear(coord) == true) { return true; } } return false; } void KoGamutMask::paint(QPainter &painter, bool preview) { QVector* shapeVector; if (preview && !d->previewShapes.isEmpty()) { shapeVector = &d->previewShapes; } else { shapeVector = &d->maskShapes; } for(KoGamutMaskShape* shape: *shapeVector) { shape->paint(painter); } } void KoGamutMask::paintStroke(QPainter &painter, bool preview) { QVector* shapeVector; if (preview && !d->previewShapes.isEmpty()) { shapeVector = &d->previewShapes; } else { shapeVector = &d->maskShapes; } for(KoGamutMaskShape* shape: *shapeVector) { shape->paintStroke(painter); } } QTransform KoGamutMask::maskToViewTransform(quint8 viewSize) { // apply mask rotation before drawing QPointF centerPoint(viewSize*0.5, viewSize*0.5); QTransform transform; transform.translate(centerPoint.x(), centerPoint.y()); transform.rotate(rotation()); transform.translate(-centerPoint.x(), -centerPoint.y()); qreal scale = viewSize/(maskSize().width()); transform.scale(scale, scale); return transform; } QTransform KoGamutMask::viewToMaskTransform(quint8 viewSize) { QPointF centerPoint(viewSize*0.5, viewSize*0.5); QTransform transform; qreal scale = viewSize/(maskSize().width()); transform.scale(1/scale, 1/scale); transform.translate(centerPoint.x(), centerPoint.y()); transform.rotate(-rotation()); transform.translate(-centerPoint.x(), -centerPoint.y()); return transform; } bool KoGamutMask::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) { Q_UNUSED(resourcesInterface); if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); d->data = dev->readAll(); // TODO: test KIS_ASSERT_RECOVER_RETURN_VALUE(d->data.size() != 0, false); if (filename().isNull()) { warnFlake << "Cannot load gamut mask" << name() << "there is no filename set"; return false; } if (d->data.isNull()) { QFile file(filename()); if (file.size() == 0) { warnFlake << "Cannot load gamut mask" << name() << "there is no data available"; return false; } file.open(QIODevice::ReadOnly); d->data = file.readAll(); file.close(); } QBuffer buf(&d->data); buf.open(QBuffer::ReadOnly); QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-krita-gamutmask", KoStore::Zip)); if (!store || store->bad()) return false; bool storeOpened = store->open("gamutmask.svg"); if (!storeOpened) { return false; } QByteArray data; data.resize(store->size()); QByteArray ba = store->read(store->size()); store->close(); QString errorMsg; int errorLine = 0; int errorColumn = 0; KoXmlDocument xmlDocument = SvgParser::createDocumentFromSvg(ba, &errorMsg, &errorLine, &errorColumn); if (xmlDocument.isNull()) { errorFlake << "Parsing error in " << filename() << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errorFlake << "Parsing error in the main document at line" << errorLine << ", column" << errorColumn << endl << "Error message: " << errorMsg; return false; } KoDocumentResourceManager manager; SvgParser parser(&manager); parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values QSizeF fragmentSize; QList shapes = parser.parseSvg(xmlDocument.documentElement(), &fragmentSize); d->maskSize = fragmentSize; d->title = parser.documentTitle(); setName(d->title); - d->description = parser.documentDescription(); + setDescription(parser.documentDescription()); setMaskShapes(shapes); if (store->open("preview.png")) { KoStoreDevice previewDev(store.data()); previewDev.open(QIODevice::ReadOnly); QImage preview = QImage(); preview.load(&previewDev, "PNG"); setImage(preview); (void)store->close(); } buf.close(); setValid(true); return true; } void KoGamutMask::setMaskShapes(QList shapes) { setMaskShapesToVector(shapes, d->maskShapes); } QList KoGamutMask::koShapes() const { QList shapes; for(KoGamutMaskShape* maskShape: d->maskShapes) { shapes.append(maskShape->koShape()); } return shapes; } bool KoGamutMask::saveToDevice(QIODevice *dev) const { KoStore* store(KoStore::createStore(dev, KoStore::Write, "application/x-krita-gamutmask", KoStore::Zip)); if (!store || store->bad()) return false; QList shapes = koShapes(); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); if (!store->open("gamutmask.svg")) { return false; } KoStoreDevice storeDev(store); storeDev.open(QIODevice::WriteOnly); SvgWriter writer(shapes); writer.setDocumentTitle(d->title); - writer.setDocumentDescription(d->description); + writer.setDocumentDescription(description()); writer.save(storeDev, d->maskSize); if (!store->close()) { return false; } if (!store->open("preview.png")) { return false; } KoStoreDevice previewDev(store); previewDev.open(QIODevice::WriteOnly); image().save(&previewDev, "PNG"); if (!store->close()) { return false; } return store->finalize() && KoResource::saveToDevice(dev); } QString KoGamutMask::title() const { return d->title; } void KoGamutMask::setTitle(QString title) { d->title = title; setName(title); } QString KoGamutMask::description() const { - return d->description; + QMap m = metadata(); + return m["description"].toString(); } void KoGamutMask::setDescription(QString description) { - d->description = description; + addMetaData("description", description); } QString KoGamutMask::defaultFileExtension() const { return ".kgm"; } int KoGamutMask::rotation() { return d->rotation; } void KoGamutMask::setRotation(int rotation) { d->rotation = rotation; } QSizeF KoGamutMask::maskSize() { return d->maskSize; } void KoGamutMask::setPreviewMaskShapes(QList shapes) { setMaskShapesToVector(shapes, d->previewShapes); } void KoGamutMask::setMaskShapesToVector(QList shapes, QVector &targetVector) { targetVector.clear(); for(KoShape* sh: shapes) { KoGamutMaskShape* maskShape = new KoGamutMaskShape(sh); targetVector.append(maskShape); } } // clean up when ending mask preview void KoGamutMask::clearPreview() { d->previewShapes.clear(); } diff --git a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp index 73497ae9d5..69dcc7fcb8 100644 --- a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp +++ b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp @@ -1,232 +1,230 @@ /* This file is part of the KDE project Copyright (c) 2017 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 #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include -#include -#include +#include #include QImage KoSvgSymbol::icon() { KoShapeGroup *group = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(group, QImage()); QRectF rc = group->boundingRect().normalized(); QImage image(rc.width(), rc.height(), QImage::Format_ARGB32_Premultiplied); QPainter gc(&image); image.fill(Qt::gray); KoShapePaintingContext ctx; // debugFlake << "Going to render. Original bounding rect:" << group->boundingRect() // << "Normalized: " << rc // << "Scale W" << 256 / rc.width() << "Scale H" << 256 / rc.height(); gc.translate(-rc.x(), -rc.y()); KoShapeManager::renderSingleShape(group, gc, ctx); gc.end(); image = image.scaled(128, 128, Qt::KeepAspectRatio); return image; } struct KoSvgSymbolCollectionResource::Private { QVector symbols; QString title; QString description; }; KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const QString& filename) : KoResource(filename) , d(new Private()) { } KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource() : KoResource(QString()) , d(new Private()) { } KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const KoSvgSymbolCollectionResource& rhs) : KoResource(QString()) , d(new Private()) { *this = rhs; } KoSvgSymbolCollectionResource &KoSvgSymbolCollectionResource::operator=(const KoSvgSymbolCollectionResource &rhs) { if (*this != rhs) { d->symbols = rhs.d->symbols; d->title = rhs.d->title; d->description = rhs.d->description; } return *this; } KoResourceSP KoSvgSymbolCollectionResource::clone() const { return KoResourceSP(new KoSvgSymbolCollectionResource(*this)); } KoSvgSymbolCollectionResource::~KoSvgSymbolCollectionResource() { } bool KoSvgSymbolCollectionResource::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) { Q_UNUSED(resourcesInterface); if (!dev->isOpen()) { dev->open(QIODevice::ReadOnly); } QByteArray ba = dev->readAll(); - KoHashGenerator *hashGenerator = KoHashGeneratorProvider::instance()->getGenerator("MD5"); - setMD5(hashGenerator->generateHash(ba)); + setMD5(KoMD5Generator::generateHash(ba)); dev->seek(0); QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc = SvgParser::createDocumentFromSvg(dev, &errorMsg, &errorLine, &errorColumn); if (doc.isNull()) { errKrita << "Parsing error in " << filename() << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); return false; } KoDocumentResourceManager manager; SvgParser parser(&manager); parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values QSizeF fragmentSize; // We're not interested in the shapes themselves qDeleteAll(parser.parseSvg(doc.documentElement(), &fragmentSize)); d->symbols = parser.takeSymbols(); // debugFlake << "Loaded" << filename() << "\n\t" // << "Title" << parser.documentTitle() << "\n\t" // << "Description" << parser.documentDescription() // << "\n\tgot" << d->symbols.size() << ResourceType::Symbols // << d->symbols[0]->shape->outlineRect() // << d->symbols[0]->shape->size(); d->title = parser.documentTitle(); setName(d->title); d->description = parser.documentDescription(); if (d->symbols.size() < 1) { setValid(false); return false; } setValid(true); setImage(d->symbols[0]->icon()); return true; } bool KoSvgSymbolCollectionResource::saveToDevice(QIODevice *dev) const { bool res = false; // XXX if (res) { KoResource::saveToDevice(dev); } return res; } QString KoSvgSymbolCollectionResource::defaultFileExtension() const { return QString(".svg"); } QString KoSvgSymbolCollectionResource::title() const { return d->title; } QString KoSvgSymbolCollectionResource::description() const { return d->description; } QString KoSvgSymbolCollectionResource::creator() const { return ""; } QString KoSvgSymbolCollectionResource::rights() const { return ""; } QString KoSvgSymbolCollectionResource::language() const { return ""; } QStringList KoSvgSymbolCollectionResource::subjects() const { return QStringList(); } QString KoSvgSymbolCollectionResource::license() const { return ""; } QStringList KoSvgSymbolCollectionResource::permits() const { return QStringList(); } QVector KoSvgSymbolCollectionResource::symbols() const { return d->symbols; } diff --git a/libs/image/KisAslStorage.cpp b/libs/image/KisAslStorage.cpp index 5c1d6f95d2..52eee293a2 100644 --- a/libs/image/KisAslStorage.cpp +++ b/libs/image/KisAslStorage.cpp @@ -1,195 +1,201 @@ /* * Copyright (C) 2018 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 "KisAslStorage.h" #include +#include +#include +#include #include struct KisAslStorageStaticRegistrar { KisAslStorageStaticRegistrar() { KisStoragePluginRegistry::instance()->addStoragePluginFactory(KisResourceStorage::StorageType::AdobeStyleLibrary, new KisStoragePluginFactory()); } }; static KisAslStorageStaticRegistrar s_registrar; class AslTagIterator : public KisResourceStorage::TagIterator { public: AslTagIterator(const QString &location, const QString &resourceType) : m_location(location) , m_resourceType(resourceType) {} bool hasNext() const override {return false; } void next() override {} QString url() const override { return QString(); } QString name() const override { return QString(); } QString comment() const override {return QString(); } KisTagSP tag() const override { return 0; } private: QString m_location; QString m_resourceType; }; class AslIterator : public KisResourceStorage::ResourceIterator { private: QString m_filename; QSharedPointer m_aslSerializer; bool m_isLoaded; QHash m_patterns; QVector m_styles; QScopedPointer> m_patternsIterator; QScopedPointer> m_stylesIterator; QString m_currentType; KoResourceSP m_currentResource; QString m_currentUuid; public: AslIterator(QSharedPointer aslSerializer, const QString& filename) : m_filename(filename) , m_aslSerializer(aslSerializer) , m_isLoaded(false) { - } bool hasNext() const override { if (!m_isLoaded) { if (!m_aslSerializer->isInitialized()) { m_aslSerializer->readFromFile(m_filename); } const_cast(this)->m_isLoaded = true; const_cast(this)->m_patterns = m_aslSerializer->patterns(); const_cast(this)->m_styles = m_aslSerializer->styles(); const_cast(this)->m_patternsIterator.reset(new QHashIterator(m_patterns)); const_cast(this)->m_stylesIterator.reset(new QVectorIterator(m_styles)); QHash layerStyles = const_cast(this)->m_aslSerializer->stylesHash(); } + if (!m_aslSerializer->isValid()) { + return false; + } return m_patternsIterator->hasNext() ? true : m_stylesIterator->hasNext(); } void next() override { if (m_patternsIterator->hasNext()) { m_currentType = ResourceType::Patterns; m_patternsIterator->next(); KoPatternSP currentPattern = m_patternsIterator->value(); m_currentResource = currentPattern; - m_currentUuid = m_patternsIterator->value()->filename(); + KIS_ASSERT(currentPattern); + m_currentUuid = currentPattern->filename(); } else if (m_stylesIterator->hasNext()) { m_currentType = ResourceType::LayerStyles; KisPSDLayerStyleSP currentLayerStyle = m_stylesIterator->next(); m_currentResource = currentLayerStyle; + KIS_ASSERT(currentLayerStyle); m_currentUuid = currentLayerStyle->filename(); } } QString url() const override { if (m_currentResource.isNull()) { return QString(); } return m_currentUuid; } QString type() const override { return m_currentResource.isNull() ? QString() : m_currentType; } QDateTime lastModified() const override { return QDateTime(); } /// This only loads the resource when called KoResourceSP resource() const override { return m_currentResource; } }; KisAslStorage::KisAslStorage(const QString &location) : KisStoragePlugin(location) , m_aslSerializer(new KisAslLayerStyleSerializer()) { - } KisAslStorage::~KisAslStorage() { } KisResourceStorage::ResourceItem KisAslStorage::resourceItem(const QString &url) { KisResourceStorage::ResourceItem item; item.url = url; item.folder = location(); item.resourceType = url.contains("pattern") ? ResourceType::Patterns : ResourceType::LayerStyles; item.lastModified = QFileInfo(location()).lastModified(); return item; } KoResourceSP KisAslStorage::resource(const QString &url) { if (!m_aslSerializer->isInitialized()) { m_aslSerializer->readFromFile(location()); } int indexOfUnderscore = url.lastIndexOf("_"); QString realUuid = url; realUuid.remove(indexOfUnderscore, url.length() - indexOfUnderscore); // remove _pattern or _style added in iterator if (url.contains("pattern")) { QHash patterns = m_aslSerializer->patterns(); if (patterns.contains(realUuid)) { return patterns[realUuid]; } } else { QHash styles = m_aslSerializer->stylesHash(); if (styles.contains(realUuid)) { return styles[realUuid]; } } return 0; } QSharedPointer KisAslStorage::resources(const QString &/*resourceType*/) { return QSharedPointer(new AslIterator(m_aslSerializer, location())); } QSharedPointer KisAslStorage::tags(const QString &resourceType) { return QSharedPointer(new AslTagIterator(location(), resourceType)); } diff --git a/libs/image/kis_asl_layer_style_serializer.cpp b/libs/image/kis_asl_layer_style_serializer.cpp index a0e85c902c..a02df3955a 100644 --- a/libs/image/kis_asl_layer_style_serializer.cpp +++ b/libs/image/kis_asl_layer_style_serializer.cpp @@ -1,1271 +1,1273 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_asl_layer_style_serializer.h" #include #include #include #include #include #include #include "kis_dom_utils.h" #include "psd.h" #include "kis_global.h" #include "asl/kis_asl_reader.h" #include "asl/kis_asl_xml_parser.h" #include "asl/kis_asl_writer_utils.h" #include "asl/kis_asl_xml_writer.h" #include "asl/kis_asl_writer.h" #include using namespace std::placeholders; KisAslLayerStyleSerializer::KisAslLayerStyleSerializer() { } KisAslLayerStyleSerializer::~KisAslLayerStyleSerializer() { } QVector KisAslLayerStyleSerializer::styles() const { return m_stylesVector; } void KisAslLayerStyleSerializer::setStyles(const QVector &styles) { m_stylesVector = styles; Q_FOREACH(const KisPSDLayerStyleSP style, styles) { m_stylesHash.insert(style->psdUuid(), style); } m_initialized = true; } QHash KisAslLayerStyleSerializer::patterns() const { return m_patternsStore; } QHash KisAslLayerStyleSerializer::stylesHash() { if (m_stylesHash.count() == 0 && m_stylesVector.count() != 0) { // build the hash Q_FOREACH(KisPSDLayerStyleSP style, m_stylesVector) { m_stylesHash.insert(style->psdUuid(), style); } } return m_stylesHash; } QString compositeOpToBlendMode(const QString &compositeOp) { QString mode = "Nrml"; if (compositeOp == COMPOSITE_OVER) { mode = "Nrml"; } else if (compositeOp == COMPOSITE_DISSOLVE) { mode = "Dslv"; } else if (compositeOp == COMPOSITE_DARKEN) { mode = "Drkn"; } else if (compositeOp == COMPOSITE_MULT) { mode = "Mltp"; } else if (compositeOp == COMPOSITE_BURN) { mode = "CBrn"; } else if (compositeOp == COMPOSITE_LINEAR_BURN) { mode = "linearBurn"; } else if (compositeOp == COMPOSITE_DARKER_COLOR) { mode = "darkerColor"; } else if (compositeOp == COMPOSITE_LIGHTEN) { mode = "Lghn"; } else if (compositeOp == COMPOSITE_SCREEN) { mode = "Scrn"; } else if (compositeOp == COMPOSITE_DODGE) { mode = "CDdg"; } else if (compositeOp == COMPOSITE_LINEAR_DODGE) { mode = "linearDodge"; } else if (compositeOp == COMPOSITE_LIGHTER_COLOR) { mode = "lighterColor"; } else if (compositeOp == COMPOSITE_OVERLAY) { mode = "Ovrl"; } else if (compositeOp == COMPOSITE_SOFT_LIGHT_PHOTOSHOP) { mode = "SftL"; } else if (compositeOp == COMPOSITE_HARD_LIGHT) { mode = "HrdL"; } else if (compositeOp == COMPOSITE_VIVID_LIGHT) { mode = "vividLight"; } else if (compositeOp == COMPOSITE_LINEAR_LIGHT) { mode = "linearLight"; } else if (compositeOp == COMPOSITE_PIN_LIGHT) { mode = "pinLight"; } else if (compositeOp == COMPOSITE_HARD_MIX_PHOTOSHOP) { mode = "hardMix"; } else if (compositeOp == COMPOSITE_DIFF) { mode = "Dfrn"; } else if (compositeOp == COMPOSITE_EXCLUSION) { mode = "Xclu"; } else if (compositeOp == COMPOSITE_SUBTRACT) { mode = "Sbtr"; } else if (compositeOp == COMPOSITE_DIVIDE) { mode = "divide"; } else if (compositeOp == COMPOSITE_HUE) { mode = "H "; } else if (compositeOp == COMPOSITE_SATURATION) { mode = "Strt"; } else if (compositeOp == COMPOSITE_COLOR) { mode = "Clr "; } else if (compositeOp == COMPOSITE_LUMINIZE) { mode = "Lmns"; } else { dbgKrita << "Unknown composite op:" << mode << "Returning \"Nrml\"!"; } return mode; } QString techniqueToString(psd_technique_type technique, const QString &typeId) { QString result = "SfBL"; switch (technique) { case psd_technique_softer: result = "SfBL"; break; case psd_technique_precise: result = "PrBL"; break; case psd_technique_slope_limit: result = "Slmt"; break; } if (typeId == "BETE" && technique == psd_technique_slope_limit) { warnKrita << "WARNING: techniqueToString: invalid technique type!" << ppVar(technique) << ppVar(typeId); } return result; } QString bevelStyleToString(psd_bevel_style style) { QString result = "OtrB"; switch (style) { case psd_bevel_outer_bevel: result = "OtrB"; break; case psd_bevel_inner_bevel: result = "InrB"; break; case psd_bevel_emboss: result = "Embs"; break; case psd_bevel_pillow_emboss: result = "PlEb"; break; case psd_bevel_stroke_emboss: result = "strokeEmboss"; break; } return result; } QString gradientTypeToString(psd_gradient_style style) { QString result = "Lnr "; switch (style) { case psd_gradient_style_linear: result = "Lnr "; break; case psd_gradient_style_radial: result = "Rdl "; break; case psd_gradient_style_angle: result = "Angl"; break; case psd_gradient_style_reflected: result = "Rflc"; break; case psd_gradient_style_diamond: result = "Dmnd"; break; } return result; } QString strokePositionToString(psd_stroke_position position) { QString result = "OutF"; switch (position) { case psd_stroke_outside: result = "OutF"; break; case psd_stroke_inside: result = "InsF"; break; case psd_stroke_center: result = "CtrF"; break; } return result; } QString strokeFillTypeToString(psd_fill_type position) { QString result = "SClr"; switch (position) { case psd_fill_solid_color: result = "SClr"; break; case psd_fill_gradient: result = "GrFl"; break; case psd_fill_pattern: result = "Ptrn"; break; } return result; } QVector KisAslLayerStyleSerializer::fetchAllPatterns(KisPSDLayerStyle *style) const { QVector allPatterns; if (style->patternOverlay()->effectEnabled()) { allPatterns << style->patternOverlay()->pattern(); } if (style->stroke()->effectEnabled() && style->stroke()->fillType() == psd_fill_pattern) { allPatterns << style->stroke()->pattern(); } if(style->bevelAndEmboss()->effectEnabled() && style->bevelAndEmboss()->textureEnabled()) { allPatterns << style->bevelAndEmboss()->texturePattern(); } return allPatterns; } QString fetchPatternUuidSafe(KoPatternSP pattern, QHash patternToUuid) { if (patternToUuid.contains(pattern)) { return patternToUuid[pattern]; } else { warnKrita << "WARNING: the pattern is not present in the Uuid map!"; return "invalid-uuid"; } } QDomDocument KisAslLayerStyleSerializer::formXmlDocument() const { KIS_ASSERT_RECOVER(!m_stylesVector.isEmpty()) { return QDomDocument(); } QVector allPatterns; Q_FOREACH (KisPSDLayerStyleSP style, m_stylesVector) { allPatterns += fetchAllPatterns(style.data()); } QHash patternToUuidMap; KisAslXmlWriter w; if (!allPatterns.isEmpty()) { w.enterList(ResourceType::Patterns); Q_FOREACH (KoPatternSP pattern, allPatterns) { if (pattern) { if (!patternToUuidMap.contains(pattern)) { QString uuid = w.writePattern("", pattern); patternToUuidMap.insert(pattern, uuid); } } else { warnKrita << "WARNING: KisAslLayerStyleSerializer::saveToDevice: saved pattern is null!"; } } w.leaveList(); } Q_FOREACH (KisPSDLayerStyleSP style, m_stylesVector) { w.enterDescriptor("", "", "null"); w.writeText("Nm ", style->name()); w.writeText("Idnt", style->psdUuid()); w.leaveDescriptor(); w.enterDescriptor("", "", "Styl"); w.enterDescriptor("documentMode", "", "documentMode"); w.leaveDescriptor(); w.enterDescriptor("Lefx", "", "Lefx"); w.writeUnitFloat("Scl ", "#Prc", 100); w.writeBoolean("masterFXSwitch", style->isEnabled()); // Drop Shadow const psd_layer_effects_drop_shadow *dropShadow = style->dropShadow(); if (dropShadow->effectEnabled()) { w.enterDescriptor("DrSh", "", "DrSh"); w.writeBoolean("enab", dropShadow->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(dropShadow->blendMode())); w.writeColor("Clr ", dropShadow->color()); w.writeUnitFloat("Opct", "#Prc", dropShadow->opacity()); w.writeBoolean("uglg", dropShadow->useGlobalLight()); w.writeUnitFloat("lagl", "#Ang", dropShadow->angle()); w.writeUnitFloat("Dstn", "#Pxl", dropShadow->distance()); w.writeUnitFloat("Ckmt", "#Pxl", dropShadow->spread()); w.writeUnitFloat("blur", "#Pxl", dropShadow->size()); w.writeUnitFloat("Nose", "#Prc", dropShadow->noise()); w.writeBoolean("AntA", dropShadow->antiAliased()); // FIXME: save curves w.writeCurve("TrnS", "Linear", QVector() << QPointF() << QPointF(255, 255)); w.writeBoolean("layerConceals", dropShadow->knocksOut()); w.leaveDescriptor(); } // Inner Shadow const psd_layer_effects_inner_shadow *innerShadow = style->innerShadow(); if (innerShadow->effectEnabled()) { w.enterDescriptor("IrSh", "", "IrSh"); w.writeBoolean("enab", innerShadow->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(innerShadow->blendMode())); w.writeColor("Clr ", innerShadow->color()); w.writeUnitFloat("Opct", "#Prc", innerShadow->opacity()); w.writeBoolean("uglg", innerShadow->useGlobalLight()); w.writeUnitFloat("lagl", "#Ang", innerShadow->angle()); w.writeUnitFloat("Dstn", "#Pxl", innerShadow->distance()); w.writeUnitFloat("Ckmt", "#Pxl", innerShadow->spread()); w.writeUnitFloat("blur", "#Pxl", innerShadow->size()); w.writeUnitFloat("Nose", "#Prc", innerShadow->noise()); w.writeBoolean("AntA", innerShadow->antiAliased()); // FIXME: save curves w.writeCurve("TrnS", "Linear", QVector() << QPointF() << QPointF(255, 255)); w.leaveDescriptor(); } // Outer Glow const psd_layer_effects_outer_glow *outerGlow = style->outerGlow(); if (outerGlow->effectEnabled()) { w.enterDescriptor("OrGl", "", "OrGl"); w.writeBoolean("enab", outerGlow->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(outerGlow->blendMode())); if (outerGlow->fillType() == psd_fill_gradient && outerGlow->gradient()) { KoSegmentGradient *segmentGradient = dynamic_cast(outerGlow->gradient().data()); KoStopGradient *stopGradient = dynamic_cast(outerGlow->gradient().data()); if (segmentGradient) { w.writeSegmentGradient("Grad", segmentGradient); } else if (stopGradient) { w.writeStopGradient("Grad", stopGradient); } else { warnKrita << "WARNING: OG: Unknown gradient type!"; w.writeColor("Clr ", outerGlow->color()); } } else { w.writeColor("Clr ", outerGlow->color()); } w.writeUnitFloat("Opct", "#Prc", outerGlow->opacity()); w.writeEnum("GlwT", "BETE", techniqueToString(outerGlow->technique(), "BETE")); w.writeUnitFloat("Ckmt", "#Pxl", outerGlow->spread()); w.writeUnitFloat("blur", "#Pxl", outerGlow->size()); w.writeUnitFloat("Nose", "#Prc", outerGlow->noise()); w.writeUnitFloat("ShdN", "#Prc", outerGlow->jitter()); w.writeBoolean("AntA", outerGlow->antiAliased()); // FIXME: save curves w.writeCurve("TrnS", "Linear", QVector() << QPointF() << QPointF(255, 255)); w.writeUnitFloat("Inpr", "#Prc", outerGlow->range()); w.leaveDescriptor(); } // Inner Glow const psd_layer_effects_inner_glow *innerGlow = style->innerGlow(); if (innerGlow->effectEnabled()) { w.enterDescriptor("IrGl", "", "IrGl"); w.writeBoolean("enab", innerGlow->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(innerGlow->blendMode())); if (innerGlow->fillType() == psd_fill_gradient && innerGlow->gradient()) { KoSegmentGradient *segmentGradient = dynamic_cast(innerGlow->gradient().data()); KoStopGradient *stopGradient = dynamic_cast(innerGlow->gradient().data()); if (segmentGradient) { w.writeSegmentGradient("Grad", segmentGradient); } else if (stopGradient) { w.writeStopGradient("Grad", stopGradient); } else { warnKrita << "WARNING: IG: Unknown gradient type!"; w.writeColor("Clr ", innerGlow->color()); } } else { w.writeColor("Clr ", innerGlow->color()); } w.writeUnitFloat("Opct", "#Prc", innerGlow->opacity()); w.writeEnum("GlwT", "BETE", techniqueToString(innerGlow->technique(), "BETE")); w.writeUnitFloat("Ckmt", "#Pxl", innerGlow->spread()); w.writeUnitFloat("blur", "#Pxl", innerGlow->size()); // NOTE: order is swapped in ASL! w.writeUnitFloat("ShdN", "#Prc", innerGlow->jitter()); w.writeUnitFloat("Nose", "#Prc", innerGlow->noise()); w.writeBoolean("AntA", innerGlow->antiAliased()); w.writeEnum("glwS", "IGSr", innerGlow->source() == psd_glow_center ? "SrcC" : "SrcE"); // FIXME: save curves w.writeCurve("TrnS", "Linear", QVector() << QPointF() << QPointF(255, 255)); w.writeUnitFloat("Inpr", "#Prc", innerGlow->range()); w.leaveDescriptor(); } // Bevel and Emboss const psd_layer_effects_bevel_emboss *bevelAndEmboss = style->bevelAndEmboss(); if (bevelAndEmboss->effectEnabled()) { w.enterDescriptor("ebbl", "", "ebbl"); w.writeBoolean("enab", bevelAndEmboss->effectEnabled()); w.writeEnum("hglM", "BlnM", compositeOpToBlendMode(bevelAndEmboss->highlightBlendMode())); w.writeColor("hglC", bevelAndEmboss->highlightColor()); w.writeUnitFloat("hglO", "#Prc", bevelAndEmboss->highlightOpacity()); w.writeEnum("sdwM", "BlnM", compositeOpToBlendMode(bevelAndEmboss->shadowBlendMode())); w.writeColor("sdwC", bevelAndEmboss->shadowColor()); w.writeUnitFloat("sdwO", "#Prc", bevelAndEmboss->shadowOpacity()); w.writeEnum("bvlT", "bvlT", techniqueToString(bevelAndEmboss->technique(), "bvlT")); w.writeEnum("bvlS", "BESl", bevelStyleToString(bevelAndEmboss->style())); w.writeBoolean("uglg", bevelAndEmboss->useGlobalLight()); w.writeUnitFloat("lagl", "#Ang", bevelAndEmboss->angle()); w.writeUnitFloat("Lald", "#Ang", bevelAndEmboss->altitude()); w.writeUnitFloat("srgR", "#Prc", bevelAndEmboss->depth()); w.writeUnitFloat("blur", "#Pxl", bevelAndEmboss->size()); w.writeEnum("bvlD", "BESs", bevelAndEmboss->direction() == psd_direction_up ? "In " : "Out "); // FIXME: save curves w.writeCurve("TrnS", "Linear", QVector() << QPointF() << QPointF(255, 255)); w.writeBoolean("antialiasGloss", bevelAndEmboss->glossAntiAliased()); w.writeUnitFloat("Sftn", "#Pxl", bevelAndEmboss->soften()); if (bevelAndEmboss->contourEnabled()) { w.writeBoolean("useShape", bevelAndEmboss->contourEnabled()); // FIXME: save curves w.writeCurve("MpgS", "Linear", QVector() << QPointF() << QPointF(255, 255)); w.writeBoolean("AntA", bevelAndEmboss->antiAliased()); w.writeUnitFloat("Inpr", "#Prc", bevelAndEmboss->contourRange()); } w.writeBoolean("useTexture", bevelAndEmboss->textureEnabled()); if (bevelAndEmboss->textureEnabled()) { w.writeBoolean("InvT", bevelAndEmboss->textureInvert()); w.writeBoolean("Algn", bevelAndEmboss->textureAlignWithLayer()); w.writeUnitFloat("Scl ", "#Prc", bevelAndEmboss->textureScale()); w.writeUnitFloat("textureDepth ", "#Prc", bevelAndEmboss->textureDepth()); w.writePatternRef("Ptrn", bevelAndEmboss->texturePattern(), fetchPatternUuidSafe(bevelAndEmboss->texturePattern(), patternToUuidMap)); w.writePhasePoint("phase", bevelAndEmboss->texturePhase()); } w.leaveDescriptor(); } // Satin const psd_layer_effects_satin *satin = style->satin(); if (satin->effectEnabled()) { w.enterDescriptor("ChFX", "", "ChFX"); w.writeBoolean("enab", satin->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(satin->blendMode())); w.writeColor("Clr ", satin->color()); w.writeBoolean("AntA", satin->antiAliased()); w.writeBoolean("Invr", satin->invert()); w.writeUnitFloat("Opct", "#Prc", satin->opacity()); w.writeUnitFloat("lagl", "#Ang", satin->angle()); w.writeUnitFloat("Dstn", "#Pxl", satin->distance()); w.writeUnitFloat("blur", "#Pxl", satin->size()); // FIXME: save curves w.writeCurve("MpgS", "Linear", QVector() << QPointF() << QPointF(255, 255)); w.leaveDescriptor(); } const psd_layer_effects_color_overlay *colorOverlay = style->colorOverlay(); if (colorOverlay->effectEnabled()) { w.enterDescriptor("SoFi", "", "SoFi"); w.writeBoolean("enab", colorOverlay->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(colorOverlay->blendMode())); w.writeUnitFloat("Opct", "#Prc", colorOverlay->opacity()); w.writeColor("Clr ", colorOverlay->color()); w.leaveDescriptor(); } // Gradient Overlay const psd_layer_effects_gradient_overlay *gradientOverlay = style->gradientOverlay(); KoSegmentGradient *segmentGradient = dynamic_cast(gradientOverlay->gradient().data()); KoStopGradient *stopGradient = dynamic_cast(gradientOverlay->gradient().data()); if (gradientOverlay->effectEnabled() && (segmentGradient || stopGradient)) { w.enterDescriptor("GrFl", "", "GrFl"); w.writeBoolean("enab", gradientOverlay->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(gradientOverlay->blendMode())); w.writeUnitFloat("Opct", "#Prc", gradientOverlay->opacity()); if (segmentGradient) { w.writeSegmentGradient("Grad", segmentGradient); } else if (stopGradient) { w.writeStopGradient("Grad", stopGradient); } w.writeUnitFloat("Angl", "#Ang", gradientOverlay->angle()); w.writeEnum("Type", "GrdT", gradientTypeToString(gradientOverlay->style())); w.writeBoolean("Rvrs", gradientOverlay->reverse()); w.writeBoolean("Algn", gradientOverlay->alignWithLayer()); w.writeUnitFloat("Scl ", "#Prc", gradientOverlay->scale()); w.writeOffsetPoint("Ofst", gradientOverlay->gradientOffset()); // FIXME: Krita doesn't support dithering w.writeBoolean("Dthr", true/*gradientOverlay->dither()*/); w.leaveDescriptor(); } // Pattern Overlay const psd_layer_effects_pattern_overlay *patternOverlay = style->patternOverlay(); if (patternOverlay->effectEnabled()) { w.enterDescriptor("patternFill", "", "patternFill"); w.writeBoolean("enab", patternOverlay->effectEnabled()); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(patternOverlay->blendMode())); w.writeUnitFloat("Opct", "#Prc", patternOverlay->opacity()); w.writePatternRef("Ptrn", patternOverlay->pattern(), fetchPatternUuidSafe(patternOverlay->pattern(), patternToUuidMap)); w.writeUnitFloat("Scl ", "#Prc", patternOverlay->scale()); w.writeBoolean("Algn", patternOverlay->alignWithLayer()); w.writePhasePoint("phase", patternOverlay->patternPhase()); w.leaveDescriptor(); } const psd_layer_effects_stroke *stroke = style->stroke(); if (stroke->effectEnabled()) { w.enterDescriptor("FrFX", "", "FrFX"); w.writeBoolean("enab", stroke->effectEnabled()); w.writeEnum("Styl", "FStl", strokePositionToString(stroke->position())); w.writeEnum("PntT", "FrFl", strokeFillTypeToString(stroke->fillType())); w.writeEnum("Md ", "BlnM", compositeOpToBlendMode(stroke->blendMode())); w.writeUnitFloat("Opct", "#Prc", stroke->opacity()); w.writeUnitFloat("Sz ", "#Pxl", stroke->size()); if (stroke->fillType() == psd_fill_solid_color) { w.writeColor("Clr ", stroke->color()); } else if (stroke->fillType() == psd_fill_gradient) { KoSegmentGradient *segmentGradient = dynamic_cast(stroke->gradient().data()); KoStopGradient *stopGradient = dynamic_cast(stroke->gradient().data()); if (segmentGradient) { w.writeSegmentGradient("Grad", segmentGradient); } else if (stopGradient) { w.writeStopGradient("Grad", stopGradient); } else { warnKrita << "WARNING: Stroke: Unknown gradient type!"; w.writeColor("Clr ", stroke->color()); } w.writeUnitFloat("Angl", "#Ang", stroke->angle()); w.writeEnum("Type", "GrdT", gradientTypeToString(stroke->style())); w.writeBoolean("Rvrs", stroke->reverse()); w.writeUnitFloat("Scl ", "#Prc", stroke->scale()); w.writeBoolean("Algn", stroke->alignWithLayer()); w.writeOffsetPoint("Ofst", stroke->gradientOffset()); // FIXME: Krita doesn't support dithering w.writeBoolean("Dthr", true/*stroke->dither()*/); } else if (stroke->fillType() == psd_fill_pattern) { w.writePatternRef("Ptrn", stroke->pattern(), fetchPatternUuidSafe(stroke->pattern(), patternToUuidMap)); w.writeUnitFloat("Scl ", "#Prc", stroke->scale()); w.writeBoolean("Lnkd", stroke->alignWithLayer()); w.writePhasePoint("phase", stroke->patternPhase()); } w.leaveDescriptor(); } w.leaveDescriptor(); w.leaveDescriptor(); } return w.document(); } inline QDomNode findNodeByClassId(const QString &classId, QDomNode parent) { return KisDomUtils::findElementByAttibute(parent, "node", "classId", classId); } void replaceAllChildren(QDomNode src, QDomNode dst) { QDomNode node; do { node = dst.lastChild(); dst.removeChild(node); } while(!node.isNull()); node = src.firstChild(); while(!node.isNull()) { dst.appendChild(node); node = src.firstChild(); } src.parentNode().removeChild(src); } QDomDocument KisAslLayerStyleSerializer::formPsdXmlDocument() const { QDomDocument doc = formXmlDocument(); QDomNode nullNode = findNodeByClassId("null", doc.documentElement()); QDomNode stylNode = findNodeByClassId("Styl", doc.documentElement()); QDomNode lefxNode = findNodeByClassId("Lefx", stylNode); replaceAllChildren(lefxNode, nullNode); return doc; } void KisAslLayerStyleSerializer::saveToDevice(QIODevice *device) { QDomDocument doc = formXmlDocument(); if (doc.isNull()) return ; KisAslWriter writer; writer.writeFile(device, doc); } void convertAndSetBlendMode(const QString &mode, boost::function setBlendMode) { QString compositeOp = COMPOSITE_OVER; if (mode == "Nrml") { compositeOp = COMPOSITE_OVER; } else if (mode == "Dslv") { compositeOp = COMPOSITE_DISSOLVE; } else if (mode == "Drkn") { compositeOp = COMPOSITE_DARKEN; } else if (mode == "Mltp") { compositeOp = COMPOSITE_MULT; } else if (mode == "CBrn") { compositeOp = COMPOSITE_BURN; } else if (mode == "linearBurn") { compositeOp = COMPOSITE_LINEAR_BURN; } else if (mode == "darkerColor") { compositeOp = COMPOSITE_DARKER_COLOR; } else if (mode == "Lghn") { compositeOp = COMPOSITE_LIGHTEN; } else if (mode == "Scrn") { compositeOp = COMPOSITE_SCREEN; } else if (mode == "CDdg") { compositeOp = COMPOSITE_DODGE; } else if (mode == "linearDodge") { compositeOp = COMPOSITE_LINEAR_DODGE; } else if (mode == "lighterColor") { compositeOp = COMPOSITE_LIGHTER_COLOR; } else if (mode == "Ovrl") { compositeOp = COMPOSITE_OVERLAY; } else if (mode == "SftL") { compositeOp = COMPOSITE_SOFT_LIGHT_PHOTOSHOP; } else if (mode == "HrdL") { compositeOp = COMPOSITE_HARD_LIGHT; } else if (mode == "vividLight") { compositeOp = COMPOSITE_VIVID_LIGHT; } else if (mode == "linearLight") { compositeOp = COMPOSITE_LINEAR_LIGHT; } else if (mode == "pinLight") { compositeOp = COMPOSITE_PIN_LIGHT; } else if (mode == "hardMix") { compositeOp = COMPOSITE_HARD_MIX_PHOTOSHOP; } else if (mode == "Dfrn") { compositeOp = COMPOSITE_DIFF; } else if (mode == "Xclu") { compositeOp = COMPOSITE_EXCLUSION; } else if (mode == "Sbtr") { compositeOp = COMPOSITE_SUBTRACT; } else if (mode == "divide") { compositeOp = COMPOSITE_DIVIDE; } else if (mode == "H ") { compositeOp = COMPOSITE_HUE; } else if (mode == "Strt") { compositeOp = COMPOSITE_SATURATION; } else if (mode == "Clr ") { compositeOp = COMPOSITE_COLOR; } else if (mode == "Lmns") { compositeOp = COMPOSITE_LUMINIZE; } else { dbgKrita << "Unknown blending mode:" << mode << "Returning COMPOSITE_OVER!"; } setBlendMode(compositeOp); } void convertAndSetCurve(const QString &name, const QVector &points, boost::function setCurveLookupTable) { Q_UNUSED(name); Q_UNUSED(points); Q_UNUSED(setCurveLookupTable); warnKrita << "convertAndSetBlendMode:" << "Curve conversion is not implemented yet"; } template void convertAndSetEnum(const QString &value, const QMap map, boost::function setMappedValue) { setMappedValue(map[value]); } inline QString _prepaddr(const QString &pref, const QString &addr) { return pref + addr; } #define CONN_TEXT_RADDR(addr, method, object, type) m_catcher.subscribeText(addr, std::bind(&type::method, object, _1)) #define CONN_COLOR(addr, method, object, type, prefix) m_catcher.subscribeColor(_prepaddr(prefix, addr), std::bind(&type::method, object, _1)) #define CONN_UNITF(addr, unit, method, object, type, prefix) m_catcher.subscribeUnitFloat(_prepaddr(prefix, addr), unit, std::bind(&type::method, object, _1)) #define CONN_BOOL(addr, method, object, type, prefix) m_catcher.subscribeBoolean(_prepaddr(prefix, addr), std::bind(&type::method, object, _1)) #define CONN_POINT(addr, method, object, type, prefix) m_catcher.subscribePoint(_prepaddr(prefix, addr), std::bind(&type::method, object, _1)) #define CONN_COMPOSITE_OP(addr, method, object, type, prefix) \ { \ boost::function setter = \ std::bind(&type::method, object, _1); \ m_catcher.subscribeEnum(_prepaddr(prefix, addr), "BlnM", std::bind(convertAndSetBlendMode, _1, setter)); \ } #define CONN_CURVE(addr, method, object, type, prefix) \ { \ boost::function setter = \ std::bind(&type::method, object, _1); \ m_catcher.subscribeCurve(_prepaddr(prefix, addr), std::bind(convertAndSetCurve, _1, _2, setter)); \ } #define CONN_ENUM(addr, tag, method, map, mapped_type, object, type, prefix) \ { \ boost::function setter = \ std::bind(&type::method, object, _1); \ m_catcher.subscribeEnum(_prepaddr(prefix, addr), tag, std::bind(convertAndSetEnum, _1, map, setter)); \ } #define CONN_GRADIENT(addr, method, object, type, prefix) \ { \ m_catcher.subscribeGradient(_prepaddr(prefix, addr), std::bind(&type::method, object, _1)); \ } #define CONN_PATTERN(addr, method, object, type, prefix) \ { \ boost::function setter = \ std::bind(&type::method, object, _1); \ m_catcher.subscribePatternRef(_prepaddr(prefix, addr), std::bind(&KisAslLayerStyleSerializer::assignPatternObject, this, _1, _2, setter)); \ } void KisAslLayerStyleSerializer::registerPatternObject(const KoPatternSP pattern, const QString& patternUuid) { if (m_patternsStore.contains(patternUuid)) { warnKrita << "WARNING: ASL style contains a duplicated pattern!" << ppVar(pattern->name()) << ppVar(m_patternsStore[patternUuid]->name()); } else { pattern->setFilename(patternUuid + QString("_pattern")); m_patternsStore.insert(patternUuid, pattern); } } void KisAslLayerStyleSerializer::assignPatternObject(const QString &patternUuid, const QString &patternName, boost::function setPattern) { Q_UNUSED(patternName); KoPatternSP pattern = m_patternsStore[patternUuid]; if (!pattern) { - warnKrita << "WARNING: ASL style contains non-existent pattern reference!"; + warnKrita << "WARNING: ASL style contains non-existent pattern reference! Searching for uuid: " + << patternUuid << " (name: " << patternName << ")"; QImage dumbImage(32, 32, QImage::Format_ARGB32); dumbImage.fill(Qt::red); KoPatternSP dumbPattern(new KoPattern(dumbImage, "invalid", "")); registerPatternObject(dumbPattern, patternUuid + QString("_invalid")); pattern = dumbPattern; + m_isValid = false; } setPattern(pattern); } class FillStylesCorrector { public: static void correct(KisPSDLayerStyle *style) { correctWithoutPattern(style->outerGlow()); correctWithoutPattern(style->innerGlow()); correctWithPattern(style->stroke()); } private: template static void correctWithPattern(T *config) { if (config->pattern()) { config->setFillType(psd_fill_pattern); } else if (config->gradient()) { config->setFillType(psd_fill_gradient); } else { config->setFillType(psd_fill_solid_color); } } template static void correctWithoutPattern(T *config) { if (config->gradient()) { config->setFillType(psd_fill_gradient); } else { config->setFillType(psd_fill_solid_color); } } }; void KisAslLayerStyleSerializer::connectCatcherToStyle(KisPSDLayerStyle *style, const QString &prefix) { CONN_TEXT_RADDR("/null/Nm ", setName, style, KisPSDLayerStyle); CONN_TEXT_RADDR("/null/Idnt", setPsdUuid, style, KisPSDLayerStyle); CONN_BOOL("/masterFXSwitch", setEnabled, style, KisPSDLayerStyle, prefix); psd_layer_effects_drop_shadow *dropShadow = style->dropShadow(); CONN_COMPOSITE_OP("/DrSh/Md ", setBlendMode, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_COLOR("/DrSh/Clr ", setColor, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_UNITF("/DrSh/Opct", "#Prc", setOpacity, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_UNITF("/DrSh/lagl", "#Ang", setAngle, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_UNITF("/DrSh/Dstn", "#Pxl", setDistance, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_UNITF("/DrSh/Ckmt", "#Pxl", setSpread, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_UNITF("/DrSh/blur", "#Pxl", setSize, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_UNITF("/DrSh/Nose", "#Prc", setNoise, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_BOOL("/DrSh/enab", setEffectEnabled, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_BOOL("/DrSh/uglg", setUseGlobalLight, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_BOOL("/DrSh/AntA", setAntiAliased, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_BOOL("/DrSh/layerConceals", setKnocksOut, dropShadow, psd_layer_effects_drop_shadow, prefix); CONN_CURVE("/DrSh/TrnS", setContourLookupTable, dropShadow, psd_layer_effects_drop_shadow, prefix); psd_layer_effects_inner_shadow *innerShadow = style->innerShadow(); CONN_COMPOSITE_OP("/IrSh/Md ", setBlendMode, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_COLOR("/IrSh/Clr ", setColor, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_UNITF("/IrSh/Opct", "#Prc", setOpacity, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_UNITF("/IrSh/lagl", "#Ang", setAngle, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_UNITF("/IrSh/Dstn", "#Pxl", setDistance, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_UNITF("/IrSh/Ckmt", "#Pxl", setSpread, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_UNITF("/IrSh/blur", "#Pxl", setSize, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_UNITF("/IrSh/Nose", "#Prc", setNoise, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_BOOL("/IrSh/enab", setEffectEnabled, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_BOOL("/IrSh/uglg", setUseGlobalLight, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_BOOL("/IrSh/AntA", setAntiAliased, innerShadow, psd_layer_effects_inner_shadow, prefix); CONN_CURVE("/IrSh/TrnS", setContourLookupTable, innerShadow, psd_layer_effects_inner_shadow, prefix); psd_layer_effects_outer_glow *outerGlow = style->outerGlow(); CONN_COMPOSITE_OP("/OrGl/Md ", setBlendMode, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_COLOR("/OrGl/Clr ", setColor, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_UNITF("/OrGl/Opct", "#Prc", setOpacity, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_UNITF("/OrGl/Ckmt", "#Pxl", setSpread, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_UNITF("/OrGl/blur", "#Pxl", setSize, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_UNITF("/OrGl/Nose", "#Prc", setNoise, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_BOOL("/OrGl/enab", setEffectEnabled, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_BOOL("/OrGl/AntA", setAntiAliased, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_CURVE("/OrGl/TrnS", setContourLookupTable, outerGlow, psd_layer_effects_outer_glow, prefix); QMap fillTechniqueMap; fillTechniqueMap.insert("PrBL", psd_technique_precise); fillTechniqueMap.insert("SfBL", psd_technique_softer); CONN_ENUM("/OrGl/GlwT", "BETE", setTechnique, fillTechniqueMap, psd_technique_type, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_GRADIENT("/OrGl/Grad", setGradient, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_UNITF("/OrGl/Inpr", "#Prc", setRange, outerGlow, psd_layer_effects_outer_glow, prefix); CONN_UNITF("/OrGl/ShdN", "#Prc", setJitter, outerGlow, psd_layer_effects_outer_glow, prefix); psd_layer_effects_inner_glow *innerGlow = style->innerGlow(); CONN_COMPOSITE_OP("/IrGl/Md ", setBlendMode, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_COLOR("/IrGl/Clr ", setColor, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_UNITF("/IrGl/Opct", "#Prc", setOpacity, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_UNITF("/IrGl/Ckmt", "#Pxl", setSpread, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_UNITF("/IrGl/blur", "#Pxl", setSize, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_UNITF("/IrGl/Nose", "#Prc", setNoise, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_BOOL("/IrGl/enab", setEffectEnabled, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_BOOL("/IrGl/AntA", setAntiAliased, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_CURVE("/IrGl/TrnS", setContourLookupTable, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_ENUM("/IrGl/GlwT", "BETE", setTechnique, fillTechniqueMap, psd_technique_type, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_GRADIENT("/IrGl/Grad", setGradient, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_UNITF("/IrGl/Inpr", "#Prc", setRange, innerGlow, psd_layer_effects_inner_glow, prefix); CONN_UNITF("/IrGl/ShdN", "#Prc", setJitter, innerGlow, psd_layer_effects_inner_glow, prefix); QMap glowSourceMap; glowSourceMap.insert("SrcC", psd_glow_center); glowSourceMap.insert("SrcE", psd_glow_edge); CONN_ENUM("/IrGl/glwS", "IGSr", setSource, glowSourceMap, psd_glow_source, innerGlow, psd_layer_effects_inner_glow, prefix); psd_layer_effects_satin *satin = style->satin(); CONN_COMPOSITE_OP("/ChFX/Md ", setBlendMode, satin, psd_layer_effects_satin, prefix); CONN_COLOR("/ChFX/Clr ", setColor, satin, psd_layer_effects_satin, prefix); CONN_UNITF("/ChFX/Opct", "#Prc", setOpacity, satin, psd_layer_effects_satin, prefix); CONN_UNITF("/ChFX/lagl", "#Ang", setAngle, satin, psd_layer_effects_satin, prefix); CONN_UNITF("/ChFX/Dstn", "#Pxl", setDistance, satin, psd_layer_effects_satin, prefix); CONN_UNITF("/ChFX/blur", "#Pxl", setSize, satin, psd_layer_effects_satin, prefix); CONN_BOOL("/ChFX/enab", setEffectEnabled, satin, psd_layer_effects_satin, prefix); CONN_BOOL("/ChFX/AntA", setAntiAliased, satin, psd_layer_effects_satin, prefix); CONN_BOOL("/ChFX/Invr", setInvert, satin, psd_layer_effects_satin, prefix); CONN_CURVE("/ChFX/MpgS", setContourLookupTable, satin, psd_layer_effects_satin, prefix); psd_layer_effects_color_overlay *colorOverlay = style->colorOverlay(); CONN_COMPOSITE_OP("/SoFi/Md ", setBlendMode, colorOverlay, psd_layer_effects_color_overlay, prefix); CONN_COLOR("/SoFi/Clr ", setColor, colorOverlay, psd_layer_effects_color_overlay, prefix); CONN_UNITF("/SoFi/Opct", "#Prc", setOpacity, colorOverlay, psd_layer_effects_color_overlay, prefix); CONN_BOOL("/SoFi/enab", setEffectEnabled, colorOverlay, psd_layer_effects_color_overlay, prefix); psd_layer_effects_gradient_overlay *gradientOverlay = style->gradientOverlay(); CONN_COMPOSITE_OP("/GrFl/Md ", setBlendMode, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_UNITF("/GrFl/Opct", "#Prc", setOpacity, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_UNITF("/GrFl/Scl ", "#Prc", setScale, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_UNITF("/GrFl/Angl", "#Ang", setAngle, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_BOOL("/GrFl/enab", setEffectEnabled, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); // CONN_BOOL("/GrFl/Dthr", setDitherNotImplemented, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_BOOL("/GrFl/Rvrs", setReverse, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_BOOL("/GrFl/Algn", setAlignWithLayer, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_POINT("/GrFl/Ofst", setGradientOffset, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); CONN_GRADIENT("/GrFl/Grad", setGradient, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); QMap gradientStyleMap; gradientStyleMap.insert("Lnr ", psd_gradient_style_linear); gradientStyleMap.insert("Rdl ", psd_gradient_style_radial); gradientStyleMap.insert("Angl", psd_gradient_style_angle); gradientStyleMap.insert("Rflc", psd_gradient_style_reflected); gradientStyleMap.insert("Dmnd", psd_gradient_style_diamond); CONN_ENUM("/GrFl/Type", "GrdT", setStyle, gradientStyleMap, psd_gradient_style, gradientOverlay, psd_layer_effects_gradient_overlay, prefix); psd_layer_effects_pattern_overlay *patternOverlay = style->patternOverlay(); CONN_BOOL("/patternFill/enab", setEffectEnabled, patternOverlay, psd_layer_effects_pattern_overlay, prefix); CONN_COMPOSITE_OP("/patternFill/Md ", setBlendMode, patternOverlay, psd_layer_effects_pattern_overlay, prefix); CONN_UNITF("/patternFill/Opct", "#Prc", setOpacity, patternOverlay, psd_layer_effects_pattern_overlay, prefix); CONN_PATTERN("/patternFill/Ptrn", setPattern, patternOverlay, psd_layer_effects_pattern_overlay, prefix); CONN_UNITF("/patternFill/Scl ", "#Prc", setScale, patternOverlay, psd_layer_effects_pattern_overlay, prefix); CONN_BOOL("/patternFill/Algn", setAlignWithLayer, patternOverlay, psd_layer_effects_pattern_overlay, prefix); CONN_POINT("/patternFill/phase", setPatternPhase, patternOverlay, psd_layer_effects_pattern_overlay, prefix); psd_layer_effects_stroke *stroke = style->stroke(); CONN_COMPOSITE_OP("/FrFX/Md ", setBlendMode, stroke, psd_layer_effects_stroke, prefix); CONN_BOOL("/FrFX/enab", setEffectEnabled, stroke, psd_layer_effects_stroke, prefix); CONN_UNITF("/FrFX/Opct", "#Prc", setOpacity, stroke, psd_layer_effects_stroke, prefix); CONN_UNITF("/FrFX/Sz ", "#Pxl", setSize, stroke, psd_layer_effects_stroke, prefix); QMap strokeStyleMap; strokeStyleMap.insert("OutF", psd_stroke_outside); strokeStyleMap.insert("InsF", psd_stroke_inside); strokeStyleMap.insert("CtrF", psd_stroke_center); CONN_ENUM("/FrFX/Styl", "FStl", setPosition, strokeStyleMap, psd_stroke_position, stroke, psd_layer_effects_stroke, prefix); QMap strokeFillType; strokeFillType.insert("SClr", psd_fill_solid_color); strokeFillType.insert("GrFl", psd_fill_gradient); strokeFillType.insert("Ptrn", psd_fill_pattern); CONN_ENUM("/FrFX/PntT", "FrFl", setFillType, strokeFillType, psd_fill_type, stroke, psd_layer_effects_stroke, prefix); // Color type CONN_COLOR("/FrFX/Clr ", setColor, stroke, psd_layer_effects_stroke, prefix); // Gradient Type CONN_GRADIENT("/FrFX/Grad", setGradient, stroke, psd_layer_effects_stroke, prefix); CONN_UNITF("/FrFX/Angl", "#Ang", setAngle, stroke, psd_layer_effects_stroke, prefix); CONN_UNITF("/FrFX/Scl ", "#Prc", setScale, stroke, psd_layer_effects_stroke, prefix); CONN_ENUM("/FrFX/Type", "GrdT", setStyle, gradientStyleMap, psd_gradient_style, stroke, psd_layer_effects_stroke, prefix); CONN_BOOL("/FrFX/Rvrs", setReverse, stroke, psd_layer_effects_stroke, prefix); CONN_BOOL("/FrFX/Algn", setAlignWithLayer, stroke, psd_layer_effects_stroke, prefix); CONN_POINT("/FrFX/Ofst", setGradientOffset, stroke, psd_layer_effects_stroke, prefix); // CONN_BOOL("/FrFX/Dthr", setDitherNotImplemented, stroke, psd_layer_effects_stroke, prefix); // Pattern type CONN_PATTERN("/FrFX/Ptrn", setPattern, stroke, psd_layer_effects_stroke, prefix); CONN_BOOL("/FrFX/Lnkd", setAlignWithLayer, stroke, psd_layer_effects_stroke, prefix); // yes, we share the params... CONN_POINT("/FrFX/phase", setPatternPhase, stroke, psd_layer_effects_stroke, prefix); psd_layer_effects_bevel_emboss *bevelAndEmboss = style->bevelAndEmboss(); CONN_BOOL("/ebbl/enab", setEffectEnabled, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_COMPOSITE_OP("/ebbl/hglM", setHighlightBlendMode, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_COLOR("/ebbl/hglC", setHighlightColor, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/hglO", "#Prc", setHighlightOpacity, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_COMPOSITE_OP("/ebbl/sdwM", setShadowBlendMode, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_COLOR("/ebbl/sdwC", setShadowColor, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/sdwO", "#Prc", setShadowOpacity, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); QMap bevelTechniqueMap; bevelTechniqueMap.insert("PrBL", psd_technique_precise); bevelTechniqueMap.insert("SfBL", psd_technique_softer); bevelTechniqueMap.insert("Slmt", psd_technique_slope_limit); CONN_ENUM("/ebbl/bvlT", "bvlT", setTechnique, bevelTechniqueMap, psd_technique_type, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); QMap bevelStyleMap; bevelStyleMap.insert("OtrB", psd_bevel_outer_bevel); bevelStyleMap.insert("InrB", psd_bevel_inner_bevel); bevelStyleMap.insert("Embs", psd_bevel_emboss); bevelStyleMap.insert("PlEb", psd_bevel_pillow_emboss); bevelStyleMap.insert("strokeEmboss", psd_bevel_stroke_emboss); CONN_ENUM("/ebbl/bvlS", "BESl", setStyle, bevelStyleMap, psd_bevel_style, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_BOOL("/ebbl/uglg", setUseGlobalLight, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/lagl", "#Ang", setAngle, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/Lald", "#Ang", setAltitude, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/srgR", "#Prc", setDepth, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/blur", "#Pxl", setSize, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); QMap bevelDirectionMap; bevelDirectionMap.insert("In ", psd_direction_up); bevelDirectionMap.insert("Out ", psd_direction_down); CONN_ENUM("/ebbl/bvlD", "BESs", setDirection, bevelDirectionMap, psd_direction, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_CURVE("/ebbl/TrnS", setContourLookupTable, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_BOOL("/ebbl/antialiasGloss", setGlossAntiAliased, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/Sftn", "#Pxl", setSoften, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); // Use shape mode CONN_BOOL("/ebbl/useShape", setContourEnabled, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_CURVE("/ebbl/MpgS", setGlossContourLookupTable, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_BOOL("/ebbl/AntA", setAntiAliased, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/Inpr", "#Prc", setContourRange, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); // Use texture mode CONN_BOOL("/ebbl/useTexture", setTextureEnabled, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_BOOL("/ebbl/InvT", setTextureInvert, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_BOOL("/ebbl/Algn", setTextureAlignWithLayer, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/Scl ", "#Prc", setTextureScale, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_UNITF("/ebbl/textureDepth", "#Prc", setTextureDepth, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_PATTERN("/ebbl/Ptrn", setTexturePattern, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); CONN_POINT("/ebbl/phase", setTexturePhase, bevelAndEmboss, psd_layer_effects_bevel_emboss, prefix); } void KisAslLayerStyleSerializer::newStyleStarted(bool isPsdStructure) { m_stylesVector.append(toQShared(new KisPSDLayerStyle())); KisPSDLayerStyleSP currentStyleSP = m_stylesVector.last(); KisPSDLayerStyle *currentStyle = currentStyleSP.data(); psd_layer_effects_context *context = currentStyleSP->context(); context->keep_original = 0; QString prefix = isPsdStructure ? "/null" : "/Styl/Lefx"; connectCatcherToStyle(currentStyle, prefix); } bool KisAslLayerStyleSerializer::readFromFile(const QString& filename) { QFile file(filename); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { dbgKrita << "Can't open file " << filename; return false; } readFromDevice(&file); m_initialized = true; file.close(); return true; } void KisAslLayerStyleSerializer::readFromDevice(QIODevice *device) { m_stylesVector.clear(); m_catcher.subscribePattern("/patterns/KisPattern", std::bind(&KisAslLayerStyleSerializer::registerPatternObject, this, _1, _2)); m_catcher.subscribePattern("/Patterns/KisPattern", std::bind(&KisAslLayerStyleSerializer::registerPatternObject, this, _1, _2)); m_catcher.subscribeNewStyleStarted(std::bind(&KisAslLayerStyleSerializer::newStyleStarted, this, false)); KisAslReader reader; QDomDocument doc = reader.readFile(device); //dbgKrita << ppVar(doc.toString()); //KisAslObjectCatcher c2; KisAslXmlParser parser; parser.parseXML(doc, m_catcher); // correct all the layer styles Q_FOREACH (KisPSDLayerStyleSP style, m_stylesVector) { FillStylesCorrector::correct(style.data()); style->setValid(!style->isEmpty()); style->setFilename(style->psdUuid() + QString("_style")); } m_initialized = true; } void KisAslLayerStyleSerializer::registerPSDPattern(const QDomDocument &doc) { KisAslCallbackObjectCatcher catcher; catcher.subscribePattern("/Patterns/KisPattern", std::bind(&KisAslLayerStyleSerializer::registerPatternObject, this, _1, _2)); catcher.subscribePattern("/patterns/KisPattern", std::bind(&KisAslLayerStyleSerializer::registerPatternObject, this, _1, _2)); //KisAslObjectCatcher c2; KisAslXmlParser parser; parser.parseXML(doc, catcher); } void KisAslLayerStyleSerializer::readFromPSDXML(const QDomDocument &doc) { // The caller prepares the document using th efollowing code // // KisAslReader reader; // QDomDocument doc = reader.readLfx2PsdSection(device); m_stylesVector.clear(); //m_catcher.subscribePattern("/Patterns/KisPattern", std::bind(&KisAslLayerStyleSerializer::registerPatternObject, this, _1)); m_catcher.subscribeNewStyleStarted(std::bind(&KisAslLayerStyleSerializer::newStyleStarted, this, true)); //KisAslObjectCatcher c2; KisAslXmlParser parser; parser.parseXML(doc, m_catcher); // correct all the layer styles Q_FOREACH (KisPSDLayerStyleSP style, m_stylesVector) { FillStylesCorrector::correct(style.data()); } } diff --git a/libs/image/kis_asl_layer_style_serializer.h b/libs/image/kis_asl_layer_style_serializer.h index 1b2ae13d37..25b89c10f0 100644 --- a/libs/image/kis_asl_layer_style_serializer.h +++ b/libs/image/kis_asl_layer_style_serializer.h @@ -1,80 +1,85 @@ /* * 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 __KIS_ASL_LAYER_STYLE_SERIALIZER_H #define __KIS_ASL_LAYER_STYLE_SERIALIZER_H #include "kritapsd_export.h" class QIODevice; class KoPattern; #include "kis_psd_layer_style.h" #include "asl/kis_asl_callback_object_catcher.h" class KRITAPSD_EXPORT KisAslLayerStyleSerializer { public: KisAslLayerStyleSerializer(); ~KisAslLayerStyleSerializer(); void saveToDevice(QIODevice *device); void readFromDevice(QIODevice *device); bool readFromFile(const QString& filename); QVector styles() const; void setStyles(const QVector &styles); QHash patterns() const; QHash stylesHash(); void registerPSDPattern(const QDomDocument &doc); void readFromPSDXML(const QDomDocument &doc); QDomDocument formXmlDocument() const; QDomDocument formPsdXmlDocument() const; bool isInitialized() { return m_initialized; } + bool isValid() { + return isInitialized() && m_isValid; + } + private: void registerPatternObject(const KoPatternSP pattern, const QString& patternUuid); void assignPatternObject(const QString &patternUuid, const QString &patternName, boost::function setPattern); QVector fetchAllPatterns(KisPSDLayerStyle *style) const; void newStyleStarted(bool isPsdStructure); void connectCatcherToStyle(KisPSDLayerStyle *style, const QString &prefix); private: QHash m_patternsStore; KisAslCallbackObjectCatcher m_catcher; QVector m_stylesVector; QHash m_stylesHash; bool m_initialized {false}; + bool m_isValid {true}; }; #endif /* __KIS_ASL_LAYER_STYLE_SERIALIZER_H */ diff --git a/libs/image/kis_psd_layer_style.cpp b/libs/image/kis_psd_layer_style.cpp index 86915a9c1f..5c6cff9bb0 100644 --- a/libs/image/kis_psd_layer_style.cpp +++ b/libs/image/kis_psd_layer_style.cpp @@ -1,301 +1,301 @@ /* * Copyright (c) 2014 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_psd_layer_style.h" #include #include #include #include #include #include "kis_global.h" struct Q_DECL_HIDDEN KisPSDLayerStyle::Private { Private() : version(-1) , effectEnabled(true) {} Private(const Private &rhs) : name(rhs.name), uuid(rhs.uuid), version(rhs.version), effectEnabled(rhs.effectEnabled), context(rhs.context), drop_shadow(rhs.drop_shadow), inner_shadow(rhs.inner_shadow), outer_glow(rhs.outer_glow), inner_glow(rhs.inner_glow), bevel_emboss(rhs.bevel_emboss), satin(rhs.satin), color_overlay(rhs.color_overlay), gradient_overlay(rhs.gradient_overlay), pattern_overlay(rhs.pattern_overlay), stroke(rhs.stroke) {} Private operator=(const Private &rhs) { if (this != &rhs) { name = rhs.name; uuid = rhs.uuid; version = rhs.version; effectEnabled = rhs.effectEnabled; context = rhs.context; drop_shadow = rhs.drop_shadow; inner_shadow = rhs.inner_shadow; outer_glow = rhs.outer_glow; inner_glow = rhs.inner_glow; bevel_emboss = rhs.bevel_emboss; satin = rhs.satin; color_overlay = rhs.color_overlay; gradient_overlay = rhs.gradient_overlay; pattern_overlay = rhs.pattern_overlay; stroke = rhs.stroke; } return *this; } QString name; QUuid uuid; quint16 version; bool effectEnabled; psd_layer_effects_context context; psd_layer_effects_drop_shadow drop_shadow; psd_layer_effects_inner_shadow inner_shadow; psd_layer_effects_outer_glow outer_glow; psd_layer_effects_inner_glow inner_glow; psd_layer_effects_bevel_emboss bevel_emboss; psd_layer_effects_satin satin; psd_layer_effects_color_overlay color_overlay; psd_layer_effects_gradient_overlay gradient_overlay; psd_layer_effects_pattern_overlay pattern_overlay; psd_layer_effects_stroke stroke; }; -KisPSDLayerStyle::KisPSDLayerStyle() - : KoEphemeralResource(QString()) +KisPSDLayerStyle::KisPSDLayerStyle(const QString &name) + : KoEphemeralResource(name) , d(new Private()) { d->name = i18n("Unnamed"); d->version = 7; } KisPSDLayerStyle::~KisPSDLayerStyle() { delete d; } KisPSDLayerStyle::KisPSDLayerStyle(const KisPSDLayerStyle &rhs) : KoEphemeralResource(rhs) , d(new Private(*rhs.d)) { setValid(valid()); } KisPSDLayerStyle KisPSDLayerStyle::operator=(const KisPSDLayerStyle &rhs) { if (this != &rhs) { *d = *rhs.d; } return *this; } bool KisPSDLayerStyle::isEnabled() const { return d->effectEnabled; } void KisPSDLayerStyle::setEnabled(bool value) { d->effectEnabled = value; } KoResourceSP KisPSDLayerStyle::clone() const { return toQShared(new KisPSDLayerStyle(*this)).dynamicCast(); } void KisPSDLayerStyle::clear() { *d = Private(); } bool KisPSDLayerStyle::isEmpty() const { return !(d->drop_shadow.effectEnabled() || d->inner_shadow.effectEnabled() || d->outer_glow.effectEnabled() || d->inner_glow.effectEnabled() || d->bevel_emboss.effectEnabled() || d->satin.effectEnabled() || d->color_overlay.effectEnabled() || d->gradient_overlay.effectEnabled() || d->pattern_overlay.effectEnabled() || d->stroke.effectEnabled()); } QString KisPSDLayerStyle::name() const { return d->name; } void KisPSDLayerStyle::setName(const QString &value) { d->name = value; dynamic_cast(this)->setName(value); } QUuid KisPSDLayerStyle::uuid() const { if (d->uuid.isNull()) { d->uuid = QUuid::createUuid(); } return d->uuid; } void KisPSDLayerStyle::setUuid(const QUuid &value) const { d->uuid = value; } QString KisPSDLayerStyle::psdUuid() const { return uuid().toString().mid(1, 36); } void KisPSDLayerStyle::setPsdUuid(const QString &value) const { setUuid(QUuid(QString("{%1}").arg(value))); } const psd_layer_effects_context* KisPSDLayerStyle::context() const { return &d->context; } const psd_layer_effects_drop_shadow* KisPSDLayerStyle::dropShadow() const { return &d->drop_shadow; } const psd_layer_effects_inner_shadow* KisPSDLayerStyle::innerShadow() const { return &d->inner_shadow; } const psd_layer_effects_outer_glow* KisPSDLayerStyle::outerGlow() const { return &d->outer_glow; } const psd_layer_effects_inner_glow* KisPSDLayerStyle::innerGlow() const { return &d->inner_glow; } const psd_layer_effects_satin* KisPSDLayerStyle::satin() const { return &d->satin; } const psd_layer_effects_color_overlay* KisPSDLayerStyle::colorOverlay() const { return &d->color_overlay; } const psd_layer_effects_gradient_overlay* KisPSDLayerStyle::gradientOverlay() const { return &d->gradient_overlay; } const psd_layer_effects_pattern_overlay* KisPSDLayerStyle::patternOverlay() const { return &d->pattern_overlay; } const psd_layer_effects_stroke* KisPSDLayerStyle::stroke() const { return &d->stroke; } const psd_layer_effects_bevel_emboss* KisPSDLayerStyle::bevelAndEmboss() const { return &d->bevel_emboss; } psd_layer_effects_context* KisPSDLayerStyle::context() { return &d->context; } psd_layer_effects_drop_shadow* KisPSDLayerStyle::dropShadow() { return &d->drop_shadow; } psd_layer_effects_inner_shadow* KisPSDLayerStyle::innerShadow() { return &d->inner_shadow; } psd_layer_effects_outer_glow* KisPSDLayerStyle::outerGlow() { return &d->outer_glow; } psd_layer_effects_inner_glow* KisPSDLayerStyle::innerGlow() { return &d->inner_glow; } psd_layer_effects_satin* KisPSDLayerStyle::satin() { return &d->satin; } psd_layer_effects_color_overlay* KisPSDLayerStyle::colorOverlay() { return &d->color_overlay; } psd_layer_effects_gradient_overlay* KisPSDLayerStyle::gradientOverlay() { return &d->gradient_overlay; } psd_layer_effects_pattern_overlay* KisPSDLayerStyle::patternOverlay() { return &d->pattern_overlay; } psd_layer_effects_stroke* KisPSDLayerStyle::stroke() { return &d->stroke; } psd_layer_effects_bevel_emboss* KisPSDLayerStyle::bevelAndEmboss() { return &d->bevel_emboss; } diff --git a/libs/image/kis_psd_layer_style.h b/libs/image/kis_psd_layer_style.h index 3dba1a6858..97c7d4f890 100644 --- a/libs/image/kis_psd_layer_style.h +++ b/libs/image/kis_psd_layer_style.h @@ -1,109 +1,109 @@ /* * Copyright (c) 2014 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_PSD_LAYER_STYLE_H #define KIS_PSD_LAYER_STYLE_H class QIODevice; class QUuid; #include #include #include #include "kis_types.h" #include "kritapsd_export.h" class KisPSDLayerStyle; typedef QSharedPointer KisPSDLayerStyleSP; /** * @brief The KisPSDLayerStyle class implements loading, saving and applying * the PSD layer effects. * * See https://www.tonton-pixel.com/Photoshop%20Additional%20File%20Formats/styles-file-format.html * */ class KRITAPSD_EXPORT KisPSDLayerStyle : public KoEphemeralResource { public: - explicit KisPSDLayerStyle(); + KisPSDLayerStyle(const QString& name = QString()); virtual ~KisPSDLayerStyle(); KisPSDLayerStyle(const KisPSDLayerStyle& rhs); KisPSDLayerStyle operator=(const KisPSDLayerStyle& rhs); - KoResourceSP clone() const; + KoResourceSP clone() const override; void clear(); QString name() const; void setName(const QString &value); QUuid uuid() const; void setUuid(const QUuid &value) const; QString psdUuid() const; void setPsdUuid(const QString &value) const; QPair resourceType() const override { return QPair(ResourceType::LayerStyles, ""); } /** * \return true if all the styles are disabled */ bool isEmpty() const; bool isEnabled() const; void setEnabled(bool value); const psd_layer_effects_context* context() const; const psd_layer_effects_drop_shadow* dropShadow() const; const psd_layer_effects_inner_shadow* innerShadow() const; const psd_layer_effects_outer_glow* outerGlow() const; const psd_layer_effects_inner_glow* innerGlow() const; const psd_layer_effects_satin* satin() const; const psd_layer_effects_color_overlay* colorOverlay() const; const psd_layer_effects_gradient_overlay* gradientOverlay() const; const psd_layer_effects_pattern_overlay* patternOverlay() const; const psd_layer_effects_stroke* stroke() const; const psd_layer_effects_bevel_emboss* bevelAndEmboss() const; psd_layer_effects_context* context(); psd_layer_effects_drop_shadow* dropShadow(); psd_layer_effects_inner_shadow* innerShadow(); psd_layer_effects_outer_glow* outerGlow(); psd_layer_effects_inner_glow* innerGlow(); psd_layer_effects_satin* satin(); psd_layer_effects_color_overlay* colorOverlay(); psd_layer_effects_gradient_overlay* gradientOverlay(); psd_layer_effects_pattern_overlay* patternOverlay(); psd_layer_effects_stroke* stroke(); psd_layer_effects_bevel_emboss* bevelAndEmboss(); private: struct Private; Private * const d; }; #endif // KIS_PSD_LAYER_STYLE_H diff --git a/libs/libkis/Krita.cpp b/libs/libkis/Krita.cpp index a744baf2f0..d7a56f5b90 100644 --- a/libs/libkis/Krita.cpp +++ b/libs/libkis/Krita.cpp @@ -1,399 +1,399 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Krita.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 "View.h" #include "Document.h" #include "Window.h" #include "Extension.h" #include "DockWidgetFactoryBase.h" #include "Filter.h" #include "InfoObject.h" #include "Resource.h" Krita* Krita::s_instance = 0; struct Krita::Private { Private() {} QList extensions; bool batchMode {false}; Notifier *notifier{new Notifier()}; }; Krita::Krita(QObject *parent) : QObject(parent) , d(new Private) { qRegisterMetaType(); connect(KisPart::instance(), SIGNAL(sigWindowAdded(KisMainWindow*)), SLOT(mainWindowAdded(KisMainWindow*))); } Krita::~Krita() { qDeleteAll(d->extensions); delete d->notifier; delete d; } QList Krita::actions() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return QList(); } KActionCollection *actionCollection = mainWindow->actionCollection(); return actionCollection->actions(); } QAction *Krita::action(const QString &name) const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } KActionCollection *actionCollection = mainWindow->actionCollection(); QAction *action = actionCollection->action(name); return action; } Document* Krita::activeDocument() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } KisView *view = mainWindow->activeView(); if (!view) { return 0; } KisDocument *document = view->document(); return new Document(document); } void Krita::setActiveDocument(Document* value) { Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == value->document().data()) { view->activateWindow(); break; } } } bool Krita::batchmode() const { return d->batchMode; } void Krita::setBatchmode(bool value) { d->batchMode = value; } QList Krita::documents() const { QList ret; foreach(QPointer doc, KisPart::instance()->documents()) { ret << new Document(doc); } return ret; } QStringList Krita::filters() const { QStringList ls = KisFilterRegistry::instance()->keys(); std::sort(ls.begin(), ls.end()); return ls; } Filter *Krita::filter(const QString &name) const { if (!filters().contains(name)) return 0; Filter *filter = new Filter(); filter->setName(name); KisFilterSP f = KisFilterRegistry::instance()->value(name); KisFilterConfigurationSP fc = f->defaultConfiguration(); InfoObject *info = new InfoObject(fc); filter->setConfiguration(info); return filter; } QStringList Krita::colorModels() const { QSet colorModelsIds; QList ids = KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(KoID id, ids) { colorModelsIds << id.id(); } return colorModelsIds.toList(); } QStringList Krita::colorDepths(const QString &colorModel) const { QSet colorDepthsIds; QList ids = KoColorSpaceRegistry::instance()->colorDepthList(colorModel, KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(KoID id, ids) { colorDepthsIds << id.id(); } return colorDepthsIds.toList(); } QStringList Krita::filterStrategies() const { return KisFilterStrategyRegistry::instance()->keys(); } QStringList Krita::profiles(const QString &colorModel, const QString &colorDepth) const { QSet profileNames; QString id = KoColorSpaceRegistry::instance()->colorSpaceId(colorModel, colorDepth); QList profiles = KoColorSpaceRegistry::instance()->profilesFor(id); Q_FOREACH(const KoColorProfile *profile, profiles) { profileNames << profile->name(); } QStringList r = profileNames.toList(); r.sort(); return r; } bool Krita::addProfile(const QString &profilePath) { KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); return iccEngine->addProfile(profilePath); } Notifier* Krita::notifier() const { return d->notifier; } QString Krita::version() const { return KritaVersionWrapper::versionString(true); } QList Krita::views() const { QList ret; foreach(QPointer view, KisPart::instance()->views()) { ret << new View(view); } return ret; } Window *Krita::activeWindow() const { KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); if (!mainWindow) { return 0; } return new Window(mainWindow); } QList Krita::windows() const { QList ret; foreach(QPointer mainWin, KisPart::instance()->mainWindows()) { ret << new Window(mainWin); } return ret; } QMap Krita::resources(const QString &type) const { QMap resources; KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(type); for (int i = 0; i < resourceModel->rowCount(); ++i) { QModelIndex idx = resourceModel->index(i, 0); int id = resourceModel->data(idx, Qt::UserRole + KisResourceModel::Id).toInt(); QString name = resourceModel->data(idx, Qt::UserRole + KisResourceModel::Name).toString(); QString filename = resourceModel->data(idx, Qt::UserRole + KisResourceModel::Filename).toString(); - QImage image = resourceModel->data(idx, Qt::UserRole + KisResourceModel::Image).value(); + QImage image = resourceModel->data(idx, Qt::UserRole + KisResourceModel::Thumbnail).value(); resources[name] = new Resource(id, type, name, filename, image, 0); } return resources; } QStringList Krita::recentDocuments() const { KConfigGroup grp = KSharedConfig::openConfig()->group(QString("RecentFiles")); QStringList keys = grp.keyList(); QStringList recentDocuments; for(int i = 0; i <= keys.filter("File").count(); i++) recentDocuments << grp.readEntry(QString("File%1").arg(i), QString("")); return recentDocuments; } Document* Krita::createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile, double resolution) { KisDocument *document = KisPart::instance()->createDocument(); KisPart::instance()->addDocument(document); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile); Q_ASSERT(cs); QColor qc(Qt::white); qc.setAlpha(0); KoColor bgColor(qc, cs); if (!document->newImage(name, width, height, cs, bgColor, KisConfig::RASTER_LAYER, 1, "", double(resolution / 72) )) { return 0; } Q_ASSERT(document->image()); return new Document(document); } Document* Krita::openDocument(const QString &filename) { KisDocument *document = KisPart::instance()->createDocument(); document->setFileBatchMode(this->batchmode()); KisPart::instance()->addDocument(document); document->openUrl(QUrl::fromLocalFile(filename), KisDocument::DontAddToRecent); document->setFileBatchMode(false); return new Document(document); } Window* Krita::openWindow() { KisMainWindow *mw = KisPart::instance()->createMainWindow(); return new Window(mw); } void Krita::addExtension(Extension* extension) { d->extensions.append(extension); } QList< Extension* > Krita::extensions() { return d->extensions; } void Krita::writeSetting(const QString &group, const QString &name, const QString &value) { KConfigGroup grp = KSharedConfig::openConfig()->group(group); grp.writeEntry(name, value); } QString Krita::readSetting(const QString &group, const QString &name, const QString &defaultValue) { KConfigGroup grp = KSharedConfig::openConfig()->group(group); return grp.readEntry(name, defaultValue); } QIcon Krita::icon(QString &iconName) const { return KisIconUtils::loadIcon(iconName); } void Krita::addDockWidgetFactory(DockWidgetFactoryBase* factory) { KoDockRegistry::instance()->add(factory); } Krita* Krita::instance() { if (!s_instance) { s_instance = new Krita; } return s_instance; } /** * Scripter.fromVariant(variant) * variant is a QVariant * returns instance of QObject-subclass * * This is a helper method for PyQt because PyQt cannot cast a variant to a QObject or QWidget */ QObject *Krita::fromVariant(const QVariant& v) { if (v.canConvert< QWidget* >()) { QObject* obj = qvariant_cast< QWidget* >(v); return obj; } else if (v.canConvert< QObject* >()) { QObject* obj = qvariant_cast< QObject* >(v); return obj; } else return 0; } QString Krita::krita_i18n(const QString &text) { return i18n(text.toUtf8().constData()); } void Krita::mainWindowAdded(KisMainWindow *kisWindow) { Q_FOREACH(Extension *extension, d->extensions) { Window window(kisWindow); extension->createActions(&window); } } diff --git a/libs/pigment/resources/KoColorSet.cpp b/libs/pigment/resources/KoColorSet.cpp index afa75a4738..d9cf6b0e23 100644 --- a/libs/pigment/resources/KoColorSet.cpp +++ b/libs/pigment/resources/KoColorSet.cpp @@ -1,1645 +1,1638 @@ /* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // qFromLittleEndian #include #include #include #include #include #include #include #include #include "KisSwatch.h" #include "KoColorSet.h" #include "KoColorSet_p.h" namespace { /** * readAllLinesSafe() reads all the lines in the byte array * using the automated UTF8 and CR/LF transformations. That * might be necessary for opening GPL palettes created on Linux * in Windows environment. */ QStringList readAllLinesSafe(QByteArray *data) { QStringList lines; QBuffer buffer(data); buffer.open(QBuffer::ReadOnly); QTextStream stream(&buffer); QString line; while (stream.readLineInto(&line)) { lines << line; } return lines; } } const QString KoColorSet::GLOBAL_GROUP_NAME = QString(); const QString KoColorSet::KPL_VERSION_ATTR = "version"; const QString KoColorSet::KPL_GROUP_ROW_COUNT_ATTR = "rows"; const QString KoColorSet::KPL_PALETTE_COLUMN_COUNT_ATTR = "columns"; const QString KoColorSet::KPL_PALETTE_NAME_ATTR = "name"; const QString KoColorSet::KPL_PALETTE_COMMENT_ATTR = "comment"; const QString KoColorSet::KPL_PALETTE_FILENAME_ATTR = "filename"; const QString KoColorSet::KPL_PALETTE_READONLY_ATTR = "readonly"; const QString KoColorSet::KPL_COLOR_MODEL_ID_ATTR = "colorModelId"; const QString KoColorSet::KPL_COLOR_DEPTH_ID_ATTR = "colorDepthId"; const QString KoColorSet::KPL_GROUP_NAME_ATTR = "name"; const QString KoColorSet::KPL_SWATCH_ROW_ATTR = "row"; const QString KoColorSet::KPL_SWATCH_COL_ATTR = "column"; const QString KoColorSet::KPL_SWATCH_NAME_ATTR = "name"; const QString KoColorSet::KPL_SWATCH_ID_ATTR = "id"; const QString KoColorSet::KPL_SWATCH_SPOT_ATTR = "spot"; const QString KoColorSet::KPL_SWATCH_BITDEPTH_ATTR = "bitdepth"; const QString KoColorSet::KPL_PALETTE_PROFILE_TAG = "Profile"; const QString KoColorSet::KPL_SWATCH_POS_TAG = "Position"; const QString KoColorSet::KPL_SWATCH_TAG = "ColorSetEntry"; const QString KoColorSet::KPL_GROUP_TAG = "Group"; const QString KoColorSet::KPL_PALETTE_TAG = "ColorSet"; const int MAXIMUM_ALLOWED_COLUMNS = 4096; KoColorSet::KoColorSet(const QString& filename) : KoResource(filename) , d(new Private(this)) { if (!filename.isEmpty()) { QFileInfo f(filename); setIsEditable(f.isWritable()); } } /// Create an copied palette KoColorSet::KoColorSet(const KoColorSet& rhs) : KoResource(rhs) , d(new Private(this)) { *this = rhs; } KoColorSet::~KoColorSet() { } KoColorSet &KoColorSet::operator=(const KoColorSet &rhs) { if (*this != rhs) { d->paletteType = rhs.d->paletteType; d->data = rhs.d->data; d->comment = rhs.d->comment; d->groupNames = rhs.d->groupNames; d->groups = rhs.d->groups; - d->isGlobal = rhs.d->isGlobal; d->isEditable = rhs.d->isEditable; } return *this; } KoResourceSP KoColorSet::clone() const { return KoResourceSP(new KoColorSet(*this)); } bool KoColorSet::load(KisResourcesInterfaceSP resourcesInterface) { const bool result = KoResource::load(resourcesInterface); setIsEditable(result && QFileInfo(filename()).isWritable()); return result; } bool KoColorSet::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) { Q_UNUSED(resourcesInterface); if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); d->data = dev->readAll(); Q_ASSERT(d->data.size() != 0); return d->init(); } - -bool KoColorSet::save() -{ - if (d->isGlobal) { - return KoResource::save(); - } else { - return true; // palette is not global, but still indicate that it's saved - } -} - bool KoColorSet::saveToDevice(QIODevice *dev) const { bool res; switch(d->paletteType) { case GPL: res = d->saveGpl(dev); break; default: res = d->saveKpl(dev); } if (res) { KoResource::saveToDevice(dev); } return res; } QByteArray KoColorSet::toByteArray() const { - QBuffer s; + QByteArray ba; + QBuffer s(&ba); s.open(QIODevice::WriteOnly); if (!saveToDevice(&s)) { warnPigment << "saving palette failed:" << name(); return QByteArray(); } s.close(); - s.open(QIODevice::ReadOnly); - QByteArray res = s.readAll(); - s.close(); - return res; + return ba; } bool KoColorSet::fromByteArray(QByteArray &data, KisResourcesInterfaceSP resourcesInterface) { QBuffer buf(&data); buf.open(QIODevice::ReadOnly); return loadFromDevice(&buf, resourcesInterface); } KoColorSet::PaletteType KoColorSet::paletteType() const { return d->paletteType; } void KoColorSet::setPaletteType(PaletteType paletteType) { d->paletteType = paletteType; QString suffix; switch(d->paletteType) { case GPL: suffix = ".gpl"; break; case ACT: suffix = ".act"; break; case RIFF_PAL: case PSP_PAL: suffix = ".pal"; break; case ACO: suffix = ".aco"; break; case XML: suffix = ".xml"; break; case KPL: suffix = ".kpl"; break; case SBZ: suffix = ".sbz"; break; default: suffix = defaultFileExtension(); } QStringList fileName = filename().split("."); fileName.last() = suffix.replace(".", ""); setFilename(fileName.join(".")); } quint32 KoColorSet::colorCount() const { int colorCount = 0; for (KisSwatchGroup &g : d->groups.values()) { colorCount += g.colorCount(); } return colorCount; } void KoColorSet::add(const KisSwatch &c, const QString &groupName) { KisSwatchGroup &modifiedGroup = d->groups.contains(groupName) ? d->groups[groupName] : d->global(); modifiedGroup.addEntry(c); } void KoColorSet::setEntry(const KisSwatch &e, int x, int y, const QString &groupName) { KisSwatchGroup &modifiedGroup = d->groups.contains(groupName) ? d->groups[groupName] : d->global(); modifiedGroup.setEntry(e, x, y); } void KoColorSet::clear() { d->groups.clear(); d->groupNames.clear(); d->groups[GLOBAL_GROUP_NAME] = KisSwatchGroup(); d->groupNames.append(GLOBAL_GROUP_NAME); } KisSwatch KoColorSet::getColorGlobal(quint32 x, quint32 y) const { for (const QString &groupName : getGroupNames()) { if (d->groups.contains(groupName)) { if ((int)y < d->groups[groupName].rowCount()) { return d->groups[groupName].getEntry(x, y); } else { y -= d->groups[groupName].rowCount(); } } } return KisSwatch(); } KisSwatch KoColorSet::getColorGroup(quint32 x, quint32 y, QString groupName) { KisSwatch e; const KisSwatchGroup &sourceGroup = groupName == QString() ? d->global() : d->groups[groupName]; if (sourceGroup.checkEntry(x, y)) { e = sourceGroup.getEntry(x, y); } return e; } QStringList KoColorSet::getGroupNames() const { if (d->groupNames.size() != d->groups.size()) { warnPigment << "mismatch between groups and the groupnames list."; return QStringList(d->groups.keys()); } return d->groupNames; } bool KoColorSet::changeGroupName(const QString &oldGroupName, const QString &newGroupName) { if (!d->groups.contains(oldGroupName)) { return false; } if (oldGroupName == newGroupName) { return true; } d->groups[newGroupName] = d->groups[oldGroupName]; d->groups.remove(oldGroupName); d->groups[newGroupName].setName(newGroupName); //rename the string in the stringlist; int index = d->groupNames.indexOf(oldGroupName); d->groupNames.replace(index, newGroupName); return true; } void KoColorSet::setColumnCount(int columns) { d->groups[GLOBAL_GROUP_NAME].setColumnCount(columns); for (KisSwatchGroup &g : d->groups.values()) { g.setColumnCount(columns); } } int KoColorSet::columnCount() const { return d->groups[GLOBAL_GROUP_NAME].columnCount(); } QString KoColorSet::comment() { return d->comment; } void KoColorSet::setComment(QString comment) { d->comment = comment; } bool KoColorSet::addGroup(const QString &groupName) { if (d->groups.contains(groupName) || getGroupNames().contains(groupName)) { return false; } d->groupNames.append(groupName); d->groups[groupName] = KisSwatchGroup(); d->groups[groupName].setName(groupName); return true; } bool KoColorSet::moveGroup(const QString &groupName, const QString &groupNameInsertBefore) { if (!d->groupNames.contains(groupName) || d->groupNames.contains(groupNameInsertBefore)==false) { return false; } if (groupNameInsertBefore != GLOBAL_GROUP_NAME && groupName != GLOBAL_GROUP_NAME) { d->groupNames.removeAt(d->groupNames.indexOf(groupName)); int index = d->groupNames.indexOf(groupNameInsertBefore); d->groupNames.insert(index, groupName); } return true; } bool KoColorSet::removeGroup(const QString &groupName, bool keepColors) { if (!d->groups.contains(groupName)) { return false; } if (groupName == GLOBAL_GROUP_NAME) { return false; } if (keepColors) { // put all colors directly below global int startingRow = d->groups[GLOBAL_GROUP_NAME].rowCount(); for (const KisSwatchGroup::SwatchInfo &info : d->groups[groupName].infoList()) { d->groups[GLOBAL_GROUP_NAME].setEntry(info.swatch, info.column, info.row + startingRow); } } d->groupNames.removeAt(d->groupNames.indexOf(groupName)); d->groups.remove(groupName); return true; } QString KoColorSet::defaultFileExtension() const { return QString(".kpl"); } int KoColorSet::rowCount() const { int res = 0; for (const QString &name : getGroupNames()) { res += d->groups[name].rowCount(); } return res; } KisSwatchGroup *KoColorSet::getGroup(const QString &name) { if (!d->groups.contains(name)) { return 0; } return &(d->groups[name]); } KisSwatchGroup *KoColorSet::getGlobalGroup() { return getGroup(GLOBAL_GROUP_NAME); } -bool KoColorSet::isGlobal() const -{ - return d->isGlobal; -} - -void KoColorSet::setIsGlobal(bool isGlobal) -{ - d->isGlobal = isGlobal; -} bool KoColorSet::isEditable() const { return d->isEditable; } void KoColorSet::setIsEditable(bool isEditable) { d->isEditable = isEditable; } KisSwatchGroup::SwatchInfo KoColorSet::getClosestColorInfo(KoColor compare, bool useGivenColorSpace) { KisSwatchGroup::SwatchInfo res; quint8 highestPercentage = 0; quint8 testPercentage = 0; for (const QString &groupName : getGroupNames()) { KisSwatchGroup *group = getGroup(groupName); for (const KisSwatchGroup::SwatchInfo &currInfo : group->infoList()) { KoColor color = currInfo.swatch.color(); if (useGivenColorSpace == true && compare.colorSpace() != color.colorSpace()) { color.convertTo(compare.colorSpace()); } else if (compare.colorSpace() != color.colorSpace()) { compare.convertTo(color.colorSpace()); } testPercentage = (255 - compare.colorSpace()->difference(compare.data(), color.data())); if (testPercentage > highestPercentage) { highestPercentage = testPercentage; res = currInfo; } } } return res; } /********************************KoColorSet::Private**************************/ KoColorSet::Private::Private(KoColorSet *a_colorSet) : colorSet(a_colorSet) { groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup(); groupNames.append(KoColorSet::GLOBAL_GROUP_NAME); } KoColorSet::PaletteType KoColorSet::Private::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; } void KoColorSet::Private::scribusParseColor(KoColorSet *set, QXmlStreamReader *xml) { KisSwatch colorEntry; // It's a color, retrieve it QXmlStreamAttributes colorProperties = xml->attributes(); QStringRef colorName = colorProperties.value("NAME"); colorEntry.setName(colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString()); // RGB or CMYK? if (colorProperties.hasAttribute("RGB")) { dbgPigment << "Color " << colorProperties.value("NAME") << ", RGB " << colorProperties.value("RGB"); KoColor currentColor(KoColorSpaceRegistry::instance()->rgb8()); QStringRef 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.data()[0] = r; currentColor.data()[1] = g; currentColor.data()[2] = b; currentColor.setOpacity(OPACITY_OPAQUE_U8); colorEntry.setColor(currentColor); set->add(colorEntry); 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"); KoColor currentColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString())); QStringRef colorValue = colorProperties.value("CMYK"); if (colorValue.length() != 9 && colorValue.at(0) != '#') { // Color is a hexadecimal number 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); 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.data()[0] = c; currentColor.data()[1] = m; currentColor.data()[2] = y; currentColor.data()[3] = k; currentColor.setOpacity(OPACITY_OPAQUE_U8); colorEntry.setColor(currentColor); set->add(colorEntry); while(xml->readNextStartElement()) { //ignore - these are all unknown or the /> element tag xml->skipCurrentElement(); } return; } } else { xml->raiseError("Unknown color space for color " + colorEntry.name()); } } bool KoColorSet::Private::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; } quint16 KoColorSet::Private::readShort(QIODevice *io) { quint16 val; quint64 read = io->read((char*)&val, 2); if (read != 2) return false; return qFromBigEndian(val); } bool KoColorSet::Private::init() { // just in case this is a reload (eg by KoEditColorSetDialog), groupNames.clear(); groups.clear(); groupNames.append(KoColorSet::GLOBAL_GROUP_NAME); groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup(); if (colorSet->filename().isNull()) { warnPigment << "Cannot load palette" << colorSet->name() << "there is no filename set"; return false; } if (data.isNull()) { QFile file(colorSet->filename()); if (file.size() == 0) { warnPigment << "Cannot load palette" << colorSet->name() << "there is no data available"; return false; } file.open(QIODevice::ReadOnly); data = file.readAll(); file.close(); } bool res = false; paletteType = detectFormat(colorSet->filename(), data); switch(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; } colorSet->setValid(res); - QImage img(global().columnCount() * 4, global().rowCount() * 4, QImage::Format_ARGB32); + int rows = 0; + for (QString groupName : groupNames) { + int lastRowGroup = 0; + for (const KisSwatchGroup::SwatchInfo &info : groups[groupName].infoList()) { + lastRowGroup = qMax(lastRowGroup, info.row); + } + rows += (lastRowGroup + 1); + } + + QImage img(global().columnCount() * 4, rows*4, QImage::Format_ARGB32); QPainter gc(&img); + int lastRow = 0; gc.fillRect(img.rect(), Qt::darkGray); - for (const KisSwatchGroup::SwatchInfo &info : global().infoList()) { - QColor c = info.swatch.color().toQColor(); - gc.fillRect(info.column * 4, info.row * 4, 4, 4, c); + for (QString groupName : groupNames) { + int lastRowGroup = 0; + for (const KisSwatchGroup::SwatchInfo &info : groups[groupName].infoList()) { + QColor c = info.swatch.color().toQColor(); + gc.fillRect(info.column * 4, (lastRow + info.row) * 4, 4, 4, c); + lastRowGroup = qMax(lastRowGroup, info.row); + } + lastRow += (lastRowGroup + 1); } colorSet->setImage(img); colorSet->setValid(res); data.clear(); return res; } bool KoColorSet::Private::saveGpl(QIODevice *dev) const { Q_ASSERT(dev->isOpen()); Q_ASSERT(dev->isWritable()); QTextStream stream(dev); stream << "GIMP Palette\nName: " << colorSet->name() << "\nColumns: " << colorSet->columnCount() << "\n#\n"; /* * Qt doesn't provide an interface to get a const reference to a QHash, that is * the underlying data structure of groups. Therefore, directly use * groups[KoColorSet::GLOBAL_GROUP_NAME] so that saveGpl can stay const */ for (int y = 0; y < groups[KoColorSet::GLOBAL_GROUP_NAME].rowCount(); y++) { for (int x = 0; x < colorSet->columnCount(); x++) { if (!groups[KoColorSet::GLOBAL_GROUP_NAME].checkEntry(x, y)) { continue; } const KisSwatch& entry = groups[KoColorSet::GLOBAL_GROUP_NAME].getEntry(x, y); 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; } bool KoColorSet::Private::loadGpl() { if (data.isEmpty() || data.isNull() || data.length() < 50) { warnPigment << "Illegal Gimp palette file: " << colorSet->filename(); return false; } quint32 index = 0; QStringList lines = readAllLinesSafe(&data); if (lines.size() < 3) { warnPigment << "Not enough lines in palette file: " << colorSet->filename(); return false; } QString columnsText; qint32 r, g, b; KisSwatch e; // Read name if (!lines[0].startsWith("GIMP") || !lines[1].toLower().contains("name")) { warnPigment << "Illegal Gimp palette file: " << colorSet->filename(); return false; } colorSet->setName(i18n(lines[1].split(":")[1].trimmed().toLatin1())); index = 2; // Read columns int columns = 0; if (lines[index].toLower().contains("columns")) { columnsText = lines[index].split(":")[1].trimmed(); columns = columnsText.toInt(); if (columns > MAXIMUM_ALLOWED_COLUMNS) { warnPigment << "Refusing to set unreasonable number of columns (" << columns << ") in GIMP Palette file " << colorSet->filename() << " - using maximum number of allowed columns instead"; global().setColumnCount(MAXIMUM_ALLOWED_COLUMNS); } else { global().setColumnCount(columns); } index = 3; } for (qint32 i = index; i < lines.size(); i++) { if (lines[i].startsWith('#')) { comment += lines[i].mid(1).trimmed() + ' '; } else if (!lines[i].isEmpty()) { QStringList a = lines[i].replace('\t', ' ').split(' ', QString::SkipEmptyParts); if (a.count() < 3) { continue; } r = qBound(0, a[0].toInt(), 255); g = qBound(0, a[1].toInt(), 255); b = qBound(0, a[2].toInt(), 255); e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); for (int i = 0; i != 3; i++) { a.pop_front(); } QString name = a.join(" "); e.setName(name.isEmpty() || name == "Untitled" ? i18n("Untitled") : name); global().addEntry(e); } } int rowCount = global().colorCount()/ global().columnCount(); if (global().colorCount() % global().columnCount()>0) { rowCount ++; } global().setRowCount(rowCount); return true; } bool KoColorSet::Private::loadAct() { QFileInfo info(colorSet->filename()); colorSet->setName(info.completeBaseName()); KisSwatch e; for (int i = 0; i < data.size(); i += 3) { quint8 r = data[i]; quint8 g = data[i+1]; quint8 b = data[i+2]; e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); global().addEntry(e); } return true; } bool KoColorSet::Private::loadRiff() { // https://worms2d.info/Palette_file QFileInfo info(colorSet->filename()); colorSet->setName(info.completeBaseName()); KisSwatch e; RiffHeader header; memcpy(&header, data.constData(), sizeof(RiffHeader)); header.colorcount = qFromBigEndian(header.colorcount); for (int i = sizeof(RiffHeader); (i < (int)(sizeof(RiffHeader) + header.colorcount) && i < data.size()); i += 4) { quint8 r = data[i]; quint8 g = data[i+1]; quint8 b = data[i+2]; e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e); } return true; } bool KoColorSet::Private::loadPsp() { QFileInfo info(colorSet->filename()); colorSet->setName(info.completeBaseName()); KisSwatch e; qint32 r, g, b; QStringList l = readAllLinesSafe(&data); 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 = qBound(0, a[0].toInt(), 255); g = qBound(0, a[1].toInt(), 255); b = qBound(0, a[2].toInt(), 255); e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); QString name = a.join(" "); e.setName(name.isEmpty() ? i18n("Untitled") : name); groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e); } return true; } bool KoColorSet::Private::loadKpl() { QBuffer buf(&data); buf.open(QBuffer::ReadOnly); QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "krita/x-colorset", 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(KPL_PALETTE_PROFILE_TAG); while (!c.isNull()) { QString name = c.attribute(KPL_PALETTE_NAME_ATTR); QString filename = c.attribute(KPL_PALETTE_FILENAME_ATTR); QString colorModelId = c.attribute(KPL_COLOR_MODEL_ID_ATTR); QString colorDepthId = c.attribute(KPL_COLOR_DEPTH_ID_ATTR); 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(); int desiredColumnCount; QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); colorSet->setName(e.attribute(KPL_PALETTE_NAME_ATTR)); colorSet->setIsEditable(e.attribute(KPL_PALETTE_READONLY_ATTR) != "true"); comment = e.attribute(KPL_PALETTE_COMMENT_ATTR); desiredColumnCount = e.attribute(KPL_PALETTE_COLUMN_COUNT_ATTR).toInt(); if (desiredColumnCount > MAXIMUM_ALLOWED_COLUMNS) { warnPigment << "Refusing to set unreasonable number of columns (" << desiredColumnCount << ") in KPL palette file " << colorSet->filename() << " - setting maximum allowed column count instead."; colorSet->setColumnCount(MAXIMUM_ALLOWED_COLUMNS); } else { colorSet->setColumnCount(desiredColumnCount); } loadKplGroup(doc, e, colorSet->getGlobalGroup()); QDomElement g = e.firstChildElement(KPL_GROUP_TAG); while (!g.isNull()) { QString groupName = g.attribute(KPL_GROUP_NAME_ATTR); colorSet->addGroup(groupName); loadKplGroup(doc, g, colorSet->getGroup(groupName)); g = g.nextSiblingElement(KPL_GROUP_TAG); } } buf.close(); return true; } bool KoColorSet::Private::loadAco() { QFileInfo info(colorSet->filename()); colorSet->setName(info.completeBaseName()); QBuffer buf(&data); buf.open(QBuffer::ReadOnly); quint16 version = readShort(&buf); quint16 numColors = readShort(&buf); KisSwatch 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 const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile(); KoColor c(KoColorSpaceRegistry::instance()->rgb16(srgb)); reinterpret_cast(c.data())[0] = ch3; reinterpret_cast(c.data())[1] = ch2; reinterpret_cast(c.data())[2] = ch1; c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 1) { // HSB QColor qc; qc.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0); KoColor c(qc, KoColorSpaceRegistry::instance()->rgb16()); c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 2) { // CMYK KoColor c(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = quint16_MAX - ch1; reinterpret_cast(c.data())[1] = quint16_MAX - ch2; reinterpret_cast(c.data())[2] = quint16_MAX - ch3; reinterpret_cast(c.data())[3] = quint16_MAX - ch4; c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 7) { // LAB KoColor c = KoColor(KoColorSpaceRegistry::instance()->lab16()); reinterpret_cast(c.data())[0] = ch3; reinterpret_cast(c.data())[1] = ch2; reinterpret_cast(c.data())[2] = ch1; c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else if (colorSpace == 8) { // GRAY KoColor c(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = ch1 * (quint16_MAX / 10000); c.setOpacity(OPACITY_OPAQUE_U8); e.setColor(c); } else { warnPigment << "Unsupported colorspace in palette" << colorSet->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.setName(Utf16Codec->toUnicode(ba)); } else { warnPigment << "Version 2 name block is the wrong size" << colorSet->filename(); } } v2 = readShort(&buf); //end marker also needs to be skipped. Q_UNUSED(v2); } if (!skip) { groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e); } } return true; } bool KoColorSet::Private::loadSbz() { QBuffer buf(&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: " << colorSet->filename() << ", SwatchBooker format"; QDomDocument doc; int errorLine, errorColumn; QString errorMessage; bool status = doc.setContent(ba, &errorMessage, &errorLine, &errorColumn); if (!status) { warnPigment << "Illegal XML palette:" << colorSet->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; colorSet->setName(colorName); dbgPigment << "Processed name of palette:" << colorSet->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")) { KisSwatch currentEntry; // Set if color is spot currentEntry.setSpotColor(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 currentEntry.setId(colorId.text()); currentEntry.setName(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() << ")"; } KoColor c(KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = l; reinterpret_cast(c.data())[1] = a; reinterpret_cast(c.data())[2] = b; c.setOpacity(OPACITY_OPAQUE_F); firstDefinition = true; currentEntry.setColor(c); 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() << ")"; } KoColor c(KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb)); reinterpret_cast(c.data())[0] = r; reinterpret_cast(c.data())[1] = g; reinterpret_cast(c.data())[2] = b; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); 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() << ")"; } KoColor c(KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Float32BitsColorDepthID.id(), QString())); reinterpret_cast(c.data())[0] = x; reinterpret_cast(c.data())[1] = y; reinterpret_cast(c.data())[2] = z; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); 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); } } KoColor color(colorSpace); reinterpret_cast(color.data())[0] = c; reinterpret_cast(color.data())[1] = m; reinterpret_cast(color.data())[2] = y; reinterpret_cast(color.data())[3] = k; color.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(color); 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); } } KoColor c(colorSpace); reinterpret_cast(c.data())[0] = g; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); 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); } } KoColor c(colorSpace); reinterpret_cast(c.data())[0] = r; reinterpret_cast(c.data())[1] = g; reinterpret_cast(c.data())[2] = b; c.setOpacity(OPACITY_OPAQUE_F); currentEntry.setColor(c); firstDefinition = true; } else { warnPigment << "Color space not implemented:" << model << "(line" << colorValueE.lineNumber() << ", column "<< colorValueE.columnNumber() << ")"; } } if (firstDefinition) { materialsBook.insert(currentEntry.id(), currentEntry); } 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)) { groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(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)) { groups[currentGroupName].addEntry(materialsBook.value(id)); } 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; } bool KoColorSet::Private::loadXml() { bool res = false; QXmlStreamReader *xml = new QXmlStreamReader(data); if (xml->readNextStartElement()) { QStringRef paletteId = xml->name(); if (QStringRef::compare(paletteId, "SCRIBUSCOLORS", Qt::CaseInsensitive) == 0) { // Scribus dbgPigment << "XML palette: " << colorSet->filename() << ", Scribus format"; res = loadScribusXmlPalette(colorSet, 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:" << colorSet->filename(); warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString(); return false; } else { dbgPigment << "XML palette parsed successfully:" << colorSet->filename(); return true; } } bool KoColorSet::Private::saveKpl(QIODevice *dev) const { QScopedPointer store(KoStore::createStore(dev, KoStore::Write, "krita/x-colorset", KoStore::Zip)); if (!store || store->bad()) return false; QSet colorSpaces; { QDomDocument doc; QDomElement root = doc.createElement(KPL_PALETTE_TAG); root.setAttribute(KPL_VERSION_ATTR, "1.0"); root.setAttribute(KPL_PALETTE_NAME_ATTR, colorSet->name()); root.setAttribute(KPL_PALETTE_COMMENT_ATTR, comment); root.setAttribute(KPL_PALETTE_READONLY_ATTR, - (colorSet->isEditable() || !colorSet->isGlobal()) ? "false" : "true"); + (colorSet->isEditable()) ? "false" : "true"); root.setAttribute(KPL_PALETTE_COLUMN_COUNT_ATTR, colorSet->columnCount()); root.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, groups[KoColorSet::GLOBAL_GROUP_NAME].rowCount()); saveKplGroup(doc, root, colorSet->getGroup(KoColorSet::GLOBAL_GROUP_NAME), colorSpaces); for (const QString &groupName : groupNames) { if (groupName == KoColorSet::GLOBAL_GROUP_NAME) { continue; } QDomElement gl = doc.createElement(KPL_GROUP_TAG); gl.setAttribute(KPL_GROUP_NAME_ATTR, groupName); root.appendChild(gl); saveKplGroup(doc, gl, colorSet->getGroup(groupName), colorSpaces); } 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"); for (const KoColorSpace *colorSpace : colorSpaces) { QString fn = QFileInfo(colorSpace->profile()->fileName()).fileName(); if (!store->open(fn)) { return false; } QByteArray profileRawData = colorSpace->profile()->rawData(); if (!store->write(profileRawData)) { return false; } if (!store->close()) { return false; } QDomElement el = doc.createElement(KPL_PALETTE_PROFILE_TAG); el.setAttribute(KPL_PALETTE_FILENAME_ATTR, fn); el.setAttribute(KPL_PALETTE_NAME_ATTR, colorSpace->profile()->name()); el.setAttribute(KPL_COLOR_MODEL_ID_ATTR, colorSpace->colorModelId().id()); el.setAttribute(KPL_COLOR_DEPTH_ID_ATTR, colorSpace->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(); } void KoColorSet::Private::saveKplGroup(QDomDocument &doc, QDomElement &groupEle, const KisSwatchGroup *group, QSet &colorSetSet) const { groupEle.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, QString::number(group->rowCount())); for (const SwatchInfoType &info : group->infoList()) { const KoColorProfile *profile = info.swatch.color().colorSpace()->profile(); // Only save non-builtin profiles.= if (!profile->fileName().isEmpty()) { colorSetSet.insert(info.swatch.color().colorSpace()); } QDomElement swatchEle = doc.createElement(KPL_SWATCH_TAG); swatchEle.setAttribute(KPL_SWATCH_NAME_ATTR, info.swatch.name()); swatchEle.setAttribute(KPL_SWATCH_ID_ATTR, info.swatch.id()); swatchEle.setAttribute(KPL_SWATCH_SPOT_ATTR, info.swatch.spotColor() ? "true" : "false"); swatchEle.setAttribute(KPL_SWATCH_BITDEPTH_ATTR, info.swatch.color().colorSpace()->colorDepthId().id()); info.swatch.color().toXML(doc, swatchEle); QDomElement positionEle = doc.createElement(KPL_SWATCH_POS_TAG); positionEle.setAttribute(KPL_SWATCH_ROW_ATTR, info.row); positionEle.setAttribute(KPL_SWATCH_COL_ATTR, info.column); swatchEle.appendChild(positionEle); groupEle.appendChild(swatchEle); } } void KoColorSet::Private::loadKplGroup(const QDomDocument &doc, const QDomElement &parentEle, KisSwatchGroup *group) { Q_UNUSED(doc); if (!parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull()) { group->setRowCount(parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).toInt()); } group->setColumnCount(colorSet->columnCount()); for (QDomElement swatchEle = parentEle.firstChildElement(KPL_SWATCH_TAG); !swatchEle.isNull(); swatchEle = swatchEle.nextSiblingElement(KPL_SWATCH_TAG)) { QString colorDepthId = swatchEle.attribute(KPL_SWATCH_BITDEPTH_ATTR, Integer8BitsColorDepthID.id()); KisSwatch entry; entry.setColor(KoColor::fromXML(swatchEle.firstChildElement(), colorDepthId)); entry.setName(swatchEle.attribute(KPL_SWATCH_NAME_ATTR)); entry.setId(swatchEle.attribute(KPL_SWATCH_ID_ATTR)); entry.setSpotColor(swatchEle.attribute(KPL_SWATCH_SPOT_ATTR, "false") == "true" ? true : false); QDomElement positionEle = swatchEle.firstChildElement(KPL_SWATCH_POS_TAG); if (!positionEle.isNull()) { int rowNumber = positionEle.attribute(KPL_SWATCH_ROW_ATTR).toInt(); int columnNumber = positionEle.attribute(KPL_SWATCH_COL_ATTR).toInt(); if (columnNumber < 0 || columnNumber >= colorSet->columnCount() || rowNumber < 0 ) { warnPigment << "Swatch" << entry.name() << "of palette" << colorSet->name() << "has invalid position."; continue; } group->setEntry(entry, columnNumber, rowNumber); } else { group->addEntry(entry); } } if (parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull() && group->colorCount() > 0 && group->columnCount() > 0 && (group->colorCount() / (group->columnCount()) + 1) < 20) { group->setRowCount((group->colorCount() / group->columnCount()) + 1); } } diff --git a/libs/pigment/resources/KoColorSet.h b/libs/pigment/resources/KoColorSet.h index abff9f934a..3a26dfbda6 100644 --- a/libs/pigment/resources/KoColorSet.h +++ b/libs/pigment/resources/KoColorSet.h @@ -1,226 +1,212 @@ /* 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 #include "KoColor.h" #include "KisSwatch.h" #include "KisSwatchGroup.h" /** * Also called palette. * Open Gimp, Photoshop or RIFF palette files. This is a straight port * from the Gimp. */ class KRITAPIGMENT_EXPORT KoColorSet :public KoResource { public: static const QString GLOBAL_GROUP_NAME; static const QString KPL_VERSION_ATTR; static const QString KPL_GROUP_ROW_COUNT_ATTR; static const QString KPL_PALETTE_COLUMN_COUNT_ATTR; static const QString KPL_PALETTE_NAME_ATTR; static const QString KPL_PALETTE_COMMENT_ATTR; static const QString KPL_PALETTE_FILENAME_ATTR; static const QString KPL_PALETTE_READONLY_ATTR; static const QString KPL_COLOR_MODEL_ID_ATTR; static const QString KPL_COLOR_DEPTH_ID_ATTR; static const QString KPL_GROUP_NAME_ATTR; static const QString KPL_SWATCH_ROW_ATTR; static const QString KPL_SWATCH_COL_ATTR; static const QString KPL_SWATCH_NAME_ATTR; static const QString KPL_SWATCH_SPOT_ATTR; static const QString KPL_SWATCH_ID_ATTR; static const QString KPL_SWATCH_BITDEPTH_ATTR; static const QString KPL_PALETTE_PROFILE_TAG; static const QString KPL_SWATCH_POS_TAG; static const QString KPL_SWATCH_TAG; static const QString KPL_GROUP_TAG; static const QString KPL_PALETTE_TAG; public: enum PaletteType { UNKNOWN = 0, GPL, // GIMP RIFF_PAL, // RIFF ACT, // Photoshop binary PSP_PAL, // PaintShop Pro ACO, // Photoshop Swatches 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, a Photoshop palette, * a Krita palette, * a Scribus palette or a SwatchBooker palette. */ explicit KoColorSet(const QString &filename = QString()); KoColorSet(const KoColorSet& rhs); ~KoColorSet() override; KoColorSet &operator=(const KoColorSet &rhs); KoResourceSP clone() const override; - bool load(KisResourcesInterfaceSP resourcesInterface) override; bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override; - bool save() override; bool saveToDevice(QIODevice* dev) const override; QString defaultFileExtension() const override; QPair resourceType() const override { return QPair(ResourceType::Palettes, ""); } void setColumnCount(int columns); int columnCount() const; void setComment(QString comment); QString comment(); int rowCount() const; quint32 colorCount() const; PaletteType paletteType() const; void setPaletteType(PaletteType paletteType); - /** - * @brief isGlobal - * A global color set is a set stored in the config directory - * Such a color set would be opened every time Krita is launched. - * - * A non-global color set, on contrary, would be stored in a kra file, - * and would only be opened when that file is opened by Krita. - * @return @c true if the set is global - */ - bool isGlobal() const; - void setIsGlobal(bool); - bool isEditable() const; void setIsEditable(bool isEditable); QByteArray toByteArray() const; bool fromByteArray(QByteArray &data, KisResourcesInterfaceSP resourcesInterface); /** * @brief Add a color to the palette. * @param c the swatch * @param groupName color to add the group to. If empty, it will be added to the unsorted. */ void add(const KisSwatch &, const QString &groupName = GLOBAL_GROUP_NAME); void setEntry(const KisSwatch &e, int x, int y, const QString &groupName = GLOBAL_GROUP_NAME); /** * @brief getColorGlobal * A function for getting a color based on a global index. Useful for iterating through all color entries. * @param x the global x index over the whole palette. * @param y the global y index over the whole palette. * @return the entry. */ KisSwatch getColorGlobal(quint32 x, quint32 y) const; /** * @brief getColorGroup * A function for getting the color from a specific group. * @param x the x index over the group. * @param y the y index over the group. * @param groupName the name of the group, will give unsorted when not defined. * @return the entry */ KisSwatch getColorGroup(quint32 x, quint32 y, QString groupName); /** * @brief getGroupNames * @return returns a list of group names, excluding the unsorted group. */ QStringList getGroupNames() const; /** * @brief getGroup * @param name * @return the group with the name given; global group if no parameter is given * null pointer if not found. */ KisSwatchGroup *getGroup(const QString &name); KisSwatchGroup *getGlobalGroup(); bool changeGroupName(const QString &oldGroupName, const QString &newGroupName); /** * @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 moveGroup * Move a group in the internal stringlist. * @param groupName the groupname to move. * @param groupNameInsertBefore the groupname to insert before. Empty means it will be added to the end. * @return */ bool moveGroup(const QString &groupName, const QString &groupNameInsertBefore = GLOBAL_GROUP_NAME); /** * @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 compare 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. */ KisSwatchGroup::SwatchInfo getClosestColorInfo(KoColor compare, bool useGivenColorSpace = true); private: class Private; const QScopedPointer d; }; typedef QSharedPointer KoColorSetSP; #endif // KOCOLORSET diff --git a/libs/pigment/resources/KoColorSet_p.h b/libs/pigment/resources/KoColorSet_p.h index 625cc38b17..a9dcf0015c 100644 --- a/libs/pigment/resources/KoColorSet_p.h +++ b/libs/pigment/resources/KoColorSet_p.h @@ -1,74 +1,73 @@ #ifndef KOCOLORSET_P_H #define KOCOLORSET_P_H #include #include #include #include #include #include #include "KoColorSet.h" struct RiffHeader { quint32 riff; quint32 size; quint32 signature; quint32 data; quint32 datasize; quint16 version; quint16 colorcount; }; class KoColorSet::Private { private: typedef KisSwatchGroup::SwatchInfo SwatchInfoType; public: Private(KoColorSet *a_colorSet); public: KisSwatchGroup &global() { Q_ASSERT(groups.contains(GLOBAL_GROUP_NAME)); return groups[GLOBAL_GROUP_NAME]; } public: 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(); public: KoColorSet *colorSet {0}; KoColorSet::PaletteType paletteType; QByteArray data; QString comment; QStringList groupNames; //names of the groups, this is used to determine the order they are in. QHash groups; //grouped colors. - bool isGlobal {true}; bool isEditable {false}; private: KoColorSet::PaletteType detectFormat(const QString &fileName, const QByteArray &ba); void scribusParseColor(KoColorSet *set, QXmlStreamReader *xml); bool loadScribusXmlPalette(KoColorSet *set, QXmlStreamReader *xml); quint16 readShort(QIODevice *io); void saveKplGroup(QDomDocument &doc, QDomElement &groupEle, const KisSwatchGroup *group, QSet &colorSetSet) const; void loadKplGroup(const QDomDocument &doc, const QDomElement &parentElement, KisSwatchGroup *group); }; #endif // KOCOLORSET_P_H diff --git a/libs/pigment/resources/KoPattern.h b/libs/pigment/resources/KoPattern.h index 8add410954..0c3d58e2b5 100644 --- a/libs/pigment/resources/KoPattern.h +++ b/libs/pigment/resources/KoPattern.h @@ -1,89 +1,89 @@ /* Copyright (c) 2000 Matthias Elter 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 KOPATTERN_H #define KOPATTERN_H #include #include #include #include class KoPattern; typedef QSharedPointer KoPatternSP; /// Write API docs here class KRITAPIGMENT_EXPORT KoPattern : public KoResource { public: /** * Creates a new KoPattern object using @p filename. No file is opened * in the constructor, you have to call load. * * @param filename the file name to save and load from. */ explicit KoPattern(const QString &filename); KoPattern(const QImage &image, const QString &name, const QString &folderName); ~KoPattern() override; KoPattern(const KoPattern &rhs); KoPattern& operator=(const KoPattern& rhs); - KoResourceSP clone() const; + KoResourceSP clone() const override; public: bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override; bool saveToDevice(QIODevice* dev) const override; bool loadPatFromDevice(QIODevice *dev); bool savePatToDevice(QIODevice* dev) const; qint32 width() const; qint32 height() const; QString defaultFileExtension() const override; QPair resourceType() const override { return QPair(ResourceType::Patterns, ""); } /** * @brief pattern the actual pattern image * @return a valid QImage. There are no guarantees to the image format. */ QImage pattern() const; private: bool init(QByteArray& data); void setPatternImage(const QImage& image); private: QImage m_pattern; mutable QByteArray m_md5; }; Q_DECLARE_METATYPE(KoPattern*) Q_DECLARE_METATYPE(QSharedPointer) #endif // KOPATTERN_H diff --git a/libs/psd/asl/kis_asl_reader.cpp b/libs/psd/asl/kis_asl_reader.cpp index b58527b4fe..8d801f5655 100644 --- a/libs/psd/asl/kis_asl_reader.cpp +++ b/libs/psd/asl/kis_asl_reader.cpp @@ -1,658 +1,658 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_asl_reader.h" #include "kis_dom_utils.h" #include #include #include #include #include #include "psd_utils.h" #include "psd.h" #include "compression.h" #include "kis_offset_on_exit_verifier.h" #include "kis_asl_writer_utils.h" #include "kis_asl_reader_utils.h" namespace Private { /** * Numerical fetch functions * * We read numbers and convert them to strings to be able to store * them in XML. */ QString readDoubleAsString(QIODevice *device) { double value = 0.0; SAFE_READ_EX(device, value); return KisDomUtils::toString(value); } QString readIntAsString(QIODevice *device) { quint32 value = 0.0; SAFE_READ_EX(device, value); return KisDomUtils::toString(value); } QString readBoolAsString(QIODevice *device) { quint8 value = 0.0; SAFE_READ_EX(device, value); return KisDomUtils::toString(value); } /** * XML generation functions * * Add a node and fill the corresponding attributes */ QDomElement appendXMLNodeCommon(const QString &key, const QString &value, const QString &type, QDomElement *parent, QDomDocument *doc) { QDomElement el = doc->createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", type); el.setAttribute("value", value); parent->appendChild(el); return el; } QDomElement appendXMLNodeCommonNoValue(const QString &key, const QString &type, QDomElement *parent, QDomDocument *doc) { QDomElement el = doc->createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", type); parent->appendChild(el); return el; } void appendIntegerXMLNode(const QString &key, const QString &value, QDomElement *parent, QDomDocument *doc) { appendXMLNodeCommon(key, value, "Integer", parent, doc); } void appendDoubleXMLNode(const QString &key, const QString &value, QDomElement *parent, QDomDocument *doc) { appendXMLNodeCommon(key, value, "Double", parent, doc); } void appendTextXMLNode(const QString &key, const QString &value, QDomElement *parent, QDomDocument *doc) { appendXMLNodeCommon(key, value, "Text", parent, doc); } void appendPointXMLNode(const QString &key, const QPointF &pt, QDomElement *parent, QDomDocument *doc) { QDomElement el = appendXMLNodeCommonNoValue(key, "Descriptor", parent, doc); el.setAttribute("classId", "CrPt"); el.setAttribute("name", ""); appendDoubleXMLNode("Hrzn", KisDomUtils::toString(pt.x()), &el, doc); appendDoubleXMLNode("Vrtc", KisDomUtils::toString(pt.x()), &el, doc); } /** * ASL -> XML parsing functions */ void readDescriptor(QIODevice *device, const QString &key, QDomElement *parent, QDomDocument *doc); void readChildObject(QIODevice *device, QDomElement *parent, QDomDocument *doc, bool skipKey = false) { using namespace KisAslReaderUtils; QString key; if (!skipKey) { key = readVarString(device); } QString OSType = readFixedString(device); //dbgKrita << "Child" << ppVar(key) << ppVar(OSType); if (OSType == "obj ") { - qFatal("no implemented"); + throw KisAslReaderUtils::ASLParseException("OSType 'obj' not implemented"); } else if (OSType == "Objc" || OSType == "GlbO") { readDescriptor(device, key, parent, doc); } else if (OSType == "VlLs") { quint32 numItems = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, numItems); QDomElement el = appendXMLNodeCommonNoValue(key, "List", parent, doc); for (quint32 i = 0; i < numItems; i++) { readChildObject(device, &el, doc, true); } } else if (OSType == "doub") { appendDoubleXMLNode(key, readDoubleAsString(device), parent, doc); } else if (OSType == "UntF") { const QString unit = readFixedString(device); const QString value = readDoubleAsString(device); QDomElement el = appendXMLNodeCommon(key, value, "UnitFloat", parent, doc); el.setAttribute("unit", unit); } else if (OSType == "TEXT") { QString unicodeString = readUnicodeString(device); appendTextXMLNode(key, unicodeString, parent, doc); } else if (OSType == "enum") { const QString typeId = readVarString(device); const QString value = readVarString(device); QDomElement el = appendXMLNodeCommon(key, value, "Enum", parent, doc); el.setAttribute("typeId", typeId); } else if (OSType == "long") { appendIntegerXMLNode(key, readIntAsString(device), parent, doc); } else if (OSType == "bool") { const QString value = readBoolAsString(device); appendXMLNodeCommon(key, value, "Boolean", parent, doc); } else if (OSType == "type") { - qFatal("no implemented"); + throw KisAslReaderUtils::ASLParseException("OSType 'type' not implemented"); } else if (OSType == "GlbC") { - qFatal("no implemented"); + throw KisAslReaderUtils::ASLParseException("OSType 'GlbC' not implemented"); } else if (OSType == "alis") { - qFatal("no implemented"); + throw KisAslReaderUtils::ASLParseException("OSType 'alis' not implemented"); } else if (OSType == "tdta") { - qFatal("no implemented"); + throw KisAslReaderUtils::ASLParseException("OSType 'tdta' not implemented"); } } void readDescriptor(QIODevice *device, const QString &key, QDomElement *parent, QDomDocument *doc) { using namespace KisAslReaderUtils; QString name = readUnicodeString(device); QString classId = readVarString(device); quint32 numChildren = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, numChildren); QDomElement el = appendXMLNodeCommonNoValue(key, "Descriptor", parent, doc); el.setAttribute("classId", classId); el.setAttribute("name", name); //dbgKrita << "Descriptor" << ppVar(key) << ppVar(classId) << ppVar(numChildren); for (quint32 i = 0; i < numChildren; i++) { readChildObject(device, &el, doc); } } QImage readVirtualArrayList(QIODevice *device, int numPlanes) { using namespace KisAslReaderUtils; quint32 arrayVersion = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, arrayVersion); if (arrayVersion != 3) { throw ASLParseException("VAList version is not '3'!"); } quint32 arrayLength = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, arrayLength); SETUP_OFFSET_VERIFIER(vaEndVerifier, device, arrayLength, 100); quint32 x0, y0, x1, y1; SAFE_READ_EX(device, y0); SAFE_READ_EX(device, x0); SAFE_READ_EX(device, y1); SAFE_READ_EX(device, x1); QRect arrayRect(x0, y0, x1 - x0, y1 - y0); quint32 numberOfChannels = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, numberOfChannels); if (numberOfChannels != 24) { throw ASLParseException("VAList: Krita doesn't support ASL files with 'numberOfChannels' flag not equal to 24 (it is not documented)!"); } // dbgKrita << ppVar(arrayVersion); // dbgKrita << ppVar(arrayLength); // dbgKrita << ppVar(arrayRect); // dbgKrita << ppVar(numberOfChannels); if (numPlanes != 1 && numPlanes != 3) { throw ASLParseException("VAList: unsupported number of planes!"); } QVector dataPlanes; dataPlanes.resize(3); for (int i = 0; i < numPlanes; i++) { quint32 arrayWritten = GARBAGE_VALUE_MARK; if (!psdread(device, &arrayWritten) || !arrayWritten) { throw ASLParseException("VAList plane has not-written flag set!"); } quint32 arrayPlaneLength = GARBAGE_VALUE_MARK; if (!psdread(device, &arrayPlaneLength) || !arrayPlaneLength) { throw ASLParseException("VAList has plane length set to zero!"); } SETUP_OFFSET_VERIFIER(planeEndVerifier, device, arrayPlaneLength, 0); qint64 nextPos = device->pos() + arrayPlaneLength; quint32 pixelDepth1 = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, pixelDepth1); quint32 x0, y0, x1, y1; SAFE_READ_EX(device, y0); SAFE_READ_EX(device, x0); SAFE_READ_EX(device, y1); SAFE_READ_EX(device, x1); QRect planeRect(x0, y0, x1 - x0, y1 - y0); if (planeRect != arrayRect) { throw ASLParseException("VAList: planes are not uniform. Not supported yet!"); } quint16 pixelDepth2 = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, pixelDepth2); quint8 useCompression = 9; SAFE_READ_EX(device, useCompression); // dbgKrita << "plane index:" << ppVar(i); // dbgKrita << ppVar(arrayWritten); // dbgKrita << ppVar(arrayPlaneLength); // dbgKrita << ppVar(pixelDepth1); // dbgKrita << ppVar(planeRect); // dbgKrita << ppVar(pixelDepth2); // dbgKrita << ppVar(useCompression); if (pixelDepth1 != pixelDepth2) { throw ASLParseException("VAList: two pixel depths of the plane are not equal (it is not documented)!"); } if (pixelDepth1 != 8) { throw ASLParseException("VAList: supported pixel depth of the plane in 8 only!"); } const int dataLength = planeRect.width() * planeRect.height(); if (useCompression == Compression::Uncompressed) { dataPlanes[i] = device->read(dataLength); } else if (useCompression == Compression::RLE) { const int numRows = planeRect.height(); QVector rowSizes; rowSizes.resize(numRows); for (int row = 0; row < numRows; row++) { quint16 rowSize = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, rowSize); rowSizes[row] = rowSize; } for (int row = 0; row < numRows; row++) { const quint16 rowSize = rowSizes[row]; QByteArray compressedData = device->read(rowSize); if (compressedData.size() != rowSize) { throw ASLParseException("VAList: failed to read compressed data!"); } QByteArray uncompressedData = Compression::uncompress(planeRect.width(), compressedData, Compression::RLE); if (uncompressedData.size() != planeRect.width()) { throw ASLParseException("VAList: failed to decompress data!"); } dataPlanes[i].append(uncompressedData); } } else { throw ASLParseException("VAList: ZIP compression is not implemented yet!"); } if (dataPlanes[i].size() != dataLength) { throw ASLParseException("VAList: failed to read/uncompress data plane!"); } device->seek(nextPos); } QImage image(arrayRect.size(), QImage::Format_ARGB32); const int dataLength = arrayRect.width() * arrayRect.height(); quint8 *dstPtr = image.bits(); for (int i = 0; i < dataLength; i++) { for (int j = 2; j >= 0; j--) { int plane = qMin(numPlanes, j); *dstPtr++ = dataPlanes[plane][i]; } *dstPtr++ = 0xFF; } #if 0 static int i = -1; i++; QString filename = QString("pattern_image_%1.png").arg(i); dbgKrita << "### dumping pattern image" << ppVar(filename); image.save(filename); #endif return image; } qint64 readPattern(QIODevice *device, QDomElement *parent, QDomDocument *doc) { using namespace KisAslReaderUtils; quint32 patternSize = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, patternSize); // patterns are always aligned by 4 bytes patternSize = KisAslWriterUtils::alignOffsetCeil(patternSize, 4); SETUP_OFFSET_VERIFIER(patternEndVerifier, device, patternSize, 0); quint32 patternVersion = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, patternVersion); if (patternVersion != 1) { throw ASLParseException("Pattern version is not \'1\'"); } quint32 patternImageMode = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, patternImageMode); quint16 patternHeight = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, patternHeight); quint16 patternWidth = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, patternWidth); QString patternName; psdread_unicodestring(device, patternName); QString patternUuid = readPascalString(device); // dbgKrita << "--"; // dbgKrita << ppVar(patternSize); // dbgKrita << ppVar(patternImageMode); // dbgKrita << ppVar(patternHeight); // dbgKrita << ppVar(patternWidth); // dbgKrita << ppVar(patternName); // dbgKrita << ppVar(patternUuid); int numPlanes = 0; psd_color_mode mode = static_cast(patternImageMode); switch (mode) { case MultiChannel: case Grayscale: numPlanes = 1; break; case RGB: numPlanes = 3; break; default: { QString msg = QString("Unsupported image mode: %1!").arg(mode); throw ASLParseException(msg); } } /** * Create XML data */ QDomElement pat = doc->createElement("node"); pat.setAttribute("classId", "KisPattern"); pat.setAttribute("type", "Descriptor"); pat.setAttribute("name", ""); QBuffer patternBuf; patternBuf.open(QIODevice::WriteOnly); { // ensure we don't keep resources for too long QString fileName = QString("%1.pat").arg(patternUuid); QImage patternImage = readVirtualArrayList(device, numPlanes); KoPattern realPattern(patternImage, patternName, fileName); realPattern.savePatToDevice(&patternBuf); } /** * We are loading the pattern and convert it into ARGB right away, * so we need not store real image mode and size of the pattern * externally. */ appendTextXMLNode("Nm ", patternName, &pat, doc); appendTextXMLNode("Idnt", patternUuid, &pat, doc); QDomCDATASection dataSection = doc->createCDATASection(qCompress(patternBuf.buffer()).toBase64()); QDomElement dataElement = doc->createElement("node"); dataElement.setAttribute("type", "KisPatternData"); dataElement.setAttribute("key", "Data"); dataElement.appendChild(dataSection); pat.appendChild(dataElement); parent->appendChild(pat); return sizeof(patternSize) + patternSize; } QDomDocument readFileImpl(QIODevice *device) { using namespace KisAslReaderUtils; QDomDocument doc; QDomElement root = doc.createElement("asl"); doc.appendChild(root); { quint16 stylesVersion = GARBAGE_VALUE_MARK; SAFE_READ_SIGNATURE_EX(device, stylesVersion, 2); } { quint32 aslSignature = GARBAGE_VALUE_MARK; const quint32 refSignature = 0x3842534c; // '8BSL' in little-endian SAFE_READ_SIGNATURE_EX(device, aslSignature, refSignature); } { quint16 patternsVersion = GARBAGE_VALUE_MARK; SAFE_READ_SIGNATURE_EX(device, patternsVersion, 3); } // Patterns { quint32 patternsSize = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, patternsSize); if (patternsSize > 0) { SETUP_OFFSET_VERIFIER(patternsSectionVerifier, device, patternsSize, 0); QDomElement patternsRoot = doc.createElement("node"); patternsRoot.setAttribute("type", "List"); patternsRoot.setAttribute("key", ResourceType::Patterns); root.appendChild(patternsRoot); try { qint64 bytesRead = 0; while (bytesRead < patternsSize) { qint64 chunk = readPattern(device, &patternsRoot, &doc); bytesRead += chunk; } } catch (ASLParseException &e) { warnKrita << "WARNING: ASL (emb. pattern):" << e.what(); } } } // Styles quint32 numStyles = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, numStyles); for (int i = 0; i < (int)numStyles; i++) { quint32 bytesToRead = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, bytesToRead); SETUP_OFFSET_VERIFIER(singleStyleSectionVerifier, device, bytesToRead, 0); { quint32 stylesFormatVersion = GARBAGE_VALUE_MARK; SAFE_READ_SIGNATURE_EX(device, stylesFormatVersion, 16); } readDescriptor(device, "", &root, &doc); { quint32 stylesFormatVersion = GARBAGE_VALUE_MARK; SAFE_READ_SIGNATURE_EX(device, stylesFormatVersion, 16); } readDescriptor(device, "", &root, &doc); } return doc; } } // namespace QDomDocument KisAslReader::readFile(QIODevice *device) { QDomDocument doc; if (device->isSequential()) { warnKrita << "WARNING: *** KisAslReader::readFile: the supplied" << "IO device is sequential. Chances are that" << "the layer style will *not* be loaded correctly!"; } try { doc = Private::readFileImpl(device); } catch (KisAslReaderUtils::ASLParseException &e) { warnKrita << "WARNING: ASL:" << e.what(); } return doc; } QDomDocument KisAslReader::readLfx2PsdSection(QIODevice *device) { QDomDocument doc; if (device->isSequential()) { warnKrita << "WARNING: *** KisAslReader::readLfx2PsdSection: the supplied" << "IO device is sequential. Chances are that" << "the layer style will *not* be loaded correctly!"; } try { { quint32 objectEffectsVersion = GARBAGE_VALUE_MARK; const quint32 ref = 0x00; SAFE_READ_SIGNATURE_EX(device, objectEffectsVersion, ref); } { quint32 descriptorVersion = GARBAGE_VALUE_MARK; const quint32 ref = 0x10; SAFE_READ_SIGNATURE_EX(device, descriptorVersion, ref); } QDomElement root = doc.createElement("asl"); doc.appendChild(root); Private::readDescriptor(device, "", &root, &doc); } catch (KisAslReaderUtils::ASLParseException &e) { warnKrita << "WARNING: PSD: lfx2 section:" << e.what(); } return doc; } QDomDocument KisAslReader::readPsdSectionPattern(QIODevice *device, qint64 bytesLeft) { QDomDocument doc; QDomElement root = doc.createElement("asl"); doc.appendChild(root); QDomElement pat = doc.createElement("node"); root.appendChild(pat); pat.setAttribute("classId", ResourceType::Patterns); pat.setAttribute("type", "Descriptor"); pat.setAttribute("name", ""); try { qint64 bytesRead = 0; while (bytesRead < bytesLeft) { qint64 chunk = Private::readPattern(device, &pat, &doc); bytesRead += chunk; } } catch (KisAslReaderUtils::ASLParseException &e) { warnKrita << "WARNING: PSD (emb. pattern):" << e.what(); } return doc; } diff --git a/libs/resources/CMakeLists.txt b/libs/resources/CMakeLists.txt index dd29595c6a..840f4a55d6 100644 --- a/libs/resources/CMakeLists.txt +++ b/libs/resources/CMakeLists.txt @@ -1,71 +1,73 @@ include_directories(${QUAZIP_INCLUDE_DIRS}) add_subdirectory(tests) set(kritaresources_LIB_SRCS KisResourceCacheDb.cpp KisResourceLoader.cpp KisResourceLoaderRegistry.cpp KisResourceLocator.cpp KisResourceStorage.cpp KisResourceModel.cpp KisTagFilterResourceProxyModel.cpp KisResourceModelProvider.cpp KisResourceTypeModel.cpp KisStorageModel.cpp + KisStorageFilterProxyModel.cpp KisResourceIterator.cpp KisResourceSearchBoxFilter.cpp KisStoragePlugin.cpp KisBundleStorage.cpp KisFolderStorage.cpp KisMemoryStorage.cpp KisTag.cpp KisTagModel.cpp KisTagModelProvider.cpp KisActiveFilterTagProxyModel.cpp KoResource.cpp KoResourceBundle.cpp KoResourceBundleManifest.cpp KoMD5Generator.cpp - KoHashGeneratorProvider.cpp KoResourcePaths.cpp + ResourceDebug.cpp + kconfigini.cpp kconfigdata.cpp kconfigbackend.cpp KisResourcesInterface.cpp KisLocalStrokeResources.cpp KisGlobalResourcesInterface.cpp ) qt5_add_resources(kritaresources_LIB_SRCS sql.qrc) add_library(kritaresources SHARED ${kritaresources_LIB_SRCS}) generate_export_header(kritaresources BASE_NAME kritaresources) target_link_libraries(kritaresources PUBLIC Qt5::Core Qt5::Widgets PRIVATE Qt5::Sql kritaversion kritaglobal kritaplugin kritastore KF5::ConfigCore KF5::CoreAddons KF5::I18n ${QUAZIP_LIBRARIES} ) set_target_properties(kritaresources PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaresources ${INSTALL_TARGETS_DEFAULT_ARGS} ) diff --git a/libs/resources/KisActiveFilterTagProxyModel.cpp b/libs/resources/KisActiveFilterTagProxyModel.cpp index 312b1954cf..b185024fa5 100644 --- a/libs/resources/KisActiveFilterTagProxyModel.cpp +++ b/libs/resources/KisActiveFilterTagProxyModel.cpp @@ -1,68 +1,66 @@ /* * Copyright (C) 2019 Agata Cacko * * 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 "KisActiveFilterTagProxyModel.h" #include #include #include struct KisActiveFilterTagProxyModel::Private { QList tags; }; KisActiveFilterTagProxyModel::KisActiveFilterTagProxyModel(QObject *parent) : QSortFilterProxyModel(parent) , d(new Private) { } KisActiveFilterTagProxyModel::~KisActiveFilterTagProxyModel() { delete d; } bool KisActiveFilterTagProxyModel::filterAcceptsColumn(int /*source_column*/, const QModelIndex &/*source_parent*/) const { return true; } bool KisActiveFilterTagProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { - //TODO: RESOURCES: make it use active - bool active = source_parent.data(Qt::UserRole + KisTagModel::Active).toBool(); return true; } bool KisActiveFilterTagProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { KisTagModel* model = qobject_cast(sourceModel()); if (model != 0) { KisTagSP left = model->tagForIndex(source_left); KisTagSP right = model->tagForIndex(source_right); return KisTag::compareNamesAndUrls(left, right); } return source_left.row() < source_right.row(); } diff --git a/libs/resources/KisActiveFilterTagProxyModel.h b/libs/resources/KisActiveFilterTagProxyModel.h index 2fdff26605..45ea438a4a 100644 --- a/libs/resources/KisActiveFilterTagProxyModel.h +++ b/libs/resources/KisActiveFilterTagProxyModel.h @@ -1,50 +1,54 @@ /* * Copyright (C) 2019 Agata Cacko * * 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_ACTIVE_FILTER_TAG_PROXY_MODEL_H #define KIS_ACTIVE_FILTER_TAG_PROXY_MODEL_H #include #include #include #include #include "kritaresources_export.h" +/** + * KisActiveFilterTagProxyModel is a sort/filter proxy model that does no filtering, + * but sorts tags by name and url. + */ class KRITARESOURCES_EXPORT KisActiveFilterTagProxyModel : public QSortFilterProxyModel { Q_OBJECT public: KisActiveFilterTagProxyModel(QObject *parent); ~KisActiveFilterTagProxyModel() override; protected: bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override; bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; private: struct Private; Private *const d; Q_DISABLE_COPY(KisActiveFilterTagProxyModel) }; #endif // KIS_ACTIVE_FILTER_TAG_PROXY_MODEL_H diff --git a/libs/resources/KisBundleStorage.cpp b/libs/resources/KisBundleStorage.cpp index 50c4d47d96..1aad71fc59 100644 --- a/libs/resources/KisBundleStorage.cpp +++ b/libs/resources/KisBundleStorage.cpp @@ -1,211 +1,211 @@ /* * Copyright (C) 2018 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 "KisBundleStorage.h" #include #include #include #include "KisResourceStorage.h" #include "KoResourceBundle.h" #include "KoResourceBundleManifest.h" class BundleTagIterator : public KisResourceStorage::TagIterator { public: BundleTagIterator(KoResourceBundle *bundle, const QString &resourceType) : m_bundle(bundle) , m_resourceType(resourceType) { QList resources = m_bundle->manifest().files(resourceType); Q_FOREACH(const KoResourceBundleManifest::ResourceReference &resourceReference, resources) { Q_FOREACH(const QString &tagname, resourceReference.tagList) { if (!m_tags.contains(tagname)){ KisTagSP tag = QSharedPointer(new KisTag()); tag->setName(tagname); tag->setComment(tagname); tag->setUrl(tagname); m_tags[tagname] = tag; } KoResourceSP resource = m_bundle->resource(resourceType, resourceReference.resourcePath); if (resource) { m_tags[tagname]->setDefaultResources(m_tags[tagname]->defaultResources() << resource->name()); } else { qWarning() << tagname << "The following resource could not be tagged:" << resourceReference.resourcePath << "from" << bundle->filename(); } } } m_tagIterator.reset(new QListIterator(m_tags.values())); } bool hasNext() const override { return m_tagIterator->hasNext(); } void next() override { const_cast(this)->m_tag = m_tagIterator->next(); } QString url() const override { return m_tag ? m_tag->url() : QString(); } QString name() const override { return m_tag ? m_tag->name() : QString(); } QString comment() const override {return m_tag ? m_tag->comment() : QString(); } KisTagSP tag() const override { return m_tag; } private: QHash m_tags; KoResourceBundle *m_bundle {0}; QString m_resourceType; QScopedPointer > m_tagIterator; KisTagSP m_tag; }; class BundleIterator : public KisResourceStorage::ResourceIterator { public: BundleIterator(KoResourceBundle *bundle, const QString &resourceType) : m_bundle(bundle) , m_resourceType(resourceType) { m_entriesIterator.reset(new QListIterator(m_bundle->manifest().files(resourceType))); } bool hasNext() const override { return m_entriesIterator->hasNext(); } void next() override { KoResourceBundleManifest::ResourceReference ref = m_entriesIterator->next(); const_cast(this)->m_resourceReference = ref; } QString url() const override { return m_resourceReference.resourcePath; } QString type() const override { return m_resourceType; } QDateTime lastModified() const override { return QFileInfo(m_bundle->filename()).lastModified(); } /// This only loads the resource when called KoResourceSP resource() const override { return m_bundle->resource(m_resourceType, m_resourceReference.resourcePath); } private: KoResourceBundle *m_bundle {0}; QString m_resourceType; QScopedPointer > m_entriesIterator; KoResourceBundleManifest::ResourceReference m_resourceReference; }; class KisBundleStorage::Private { public: QScopedPointer bundle; }; KisBundleStorage::KisBundleStorage(const QString &location) : KisStoragePlugin(location) , d(new Private()) { d->bundle.reset(new KoResourceBundle(location)); if (!d->bundle->load()) { qWarning() << "Could not load bundle" << location; } } KisBundleStorage::~KisBundleStorage() { } KisResourceStorage::ResourceItem KisBundleStorage::resourceItem(const QString &url) { KisResourceStorage::ResourceItem item; item.url = url; QStringList parts = url.split('/', QString::SkipEmptyParts); Q_ASSERT(parts.size() == 2); item.folder = parts[0]; item.resourceType = parts[0]; item.lastModified = QFileInfo(d->bundle->filename()).lastModified(); return item; } KoResourceSP KisBundleStorage::resource(const QString &url) { QStringList parts = url.split('/', QString::SkipEmptyParts); Q_ASSERT(parts.size() == 2); return d->bundle->resource(parts[0], url); } QSharedPointer KisBundleStorage::resources(const QString &resourceType) { return QSharedPointer(new BundleIterator(d->bundle.data(), resourceType)); } QSharedPointer KisBundleStorage::tags(const QString &resourceType) { return QSharedPointer(new BundleTagIterator(d->bundle.data(), resourceType)); } QImage KisBundleStorage::thumbnail() const { return d->bundle->image(); } QStringList KisBundleStorage::metaDataKeys() const { return QStringList() << KisResourceStorage::s_meta_generator << KisResourceStorage::s_meta_author << KisResourceStorage::s_meta_title << KisResourceStorage::s_meta_description << KisResourceStorage::s_meta_initial_creator << KisResourceStorage::s_meta_creator << KisResourceStorage::s_meta_creation_date << KisResourceStorage::s_meta_dc_date << KisResourceStorage::s_meta_user_defined << KisResourceStorage::s_meta_name << KisResourceStorage::s_meta_value << KisResourceStorage::s_meta_version; } -QString KisBundleStorage::metaData(const QString &key) const +QVariant KisBundleStorage::metaData(const QString &key) const { return d->bundle->metaData(key); } diff --git a/libs/resources/KisBundleStorage.h b/libs/resources/KisBundleStorage.h index f43564c754..2675b053e5 100644 --- a/libs/resources/KisBundleStorage.h +++ b/libs/resources/KisBundleStorage.h @@ -1,56 +1,61 @@ /* * Copyright (C) 2018 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 KISBUNDLESTORAGE_H #define KISBUNDLESTORAGE_H #include #include "kritaresources_export.h" +/** + * KisBundleStorage is KisStoragePlugin that can load resources + * from bundles. It can also manage overrridden resources from bundles, + * which are not stored in the bundles themselves. + */ class KRITARESOURCES_EXPORT KisBundleStorage : public KisStoragePlugin { public: KisBundleStorage(const QString &location); virtual ~KisBundleStorage(); KisResourceStorage::ResourceItem resourceItem(const QString &url) override; /// Note: this should find resources in a folder that override a resource in the bundle first KoResourceSP resource(const QString &url) override; QSharedPointer resources(const QString &resourceType) override; QSharedPointer tags(const QString &resourceType) override; QImage thumbnail() const override; QStringList metaDataKeys() const override; - QString metaData(const QString &key) const override; + QVariant metaData(const QString &key) const override; /// Add a tag to this bundle: note, the bundle itself should NOT be rewritten, but we need to /// put these tags in a place in the file system bool addTag(const QString &resourceType, KisTagSP tag) override {Q_UNUSED(resourceType); Q_UNUSED(tag); return false;} /// Add a resource to this bundle: note, the bundle itself should NOT be rewritten, but we need to /// put these tags in a place in the file system bool addResource(const QString &resourceType, KoResourceSP resource) override {Q_UNUSED(resourceType); Q_UNUSED(resource); return false;} private: class Private; QScopedPointer d; }; #endif // KISBUNDLESTORAGE_H diff --git a/libs/resources/KisFolderStorage.cpp b/libs/resources/KisFolderStorage.cpp index 9cae96cbdc..1d4f207be7 100644 --- a/libs/resources/KisFolderStorage.cpp +++ b/libs/resources/KisFolderStorage.cpp @@ -1,266 +1,278 @@ /* * Copyright (C) 2018 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 "KisFolderStorage.h" #include #include #include #include #include #include #include class FolderTagIterator : public KisResourceStorage::TagIterator { public: FolderTagIterator(const QString &location, const QString &resourceType) : m_location(location) , m_resourceType(resourceType) { m_dirIterator.reset(new QDirIterator(location + '/' + resourceType, QStringList() << "*.tag", QDir::Files | QDir::Readable, QDirIterator::Subdirectories)); } bool hasNext() const override { return m_dirIterator->hasNext(); } void next() override { m_dirIterator->next(); const_cast(this)->m_tag.reset(new KisTag); load(m_tag); } QString url() const override { return m_tag ? m_tag->url() : QString(); } QString name() const override { return m_tag ? m_tag->name() : QString(); } QString comment() const override {return m_tag ? m_tag->comment() : QString(); } KisTagSP tag() const override { return m_tag; } private: bool load(KisTagSP tag) const { QFile f(m_dirIterator->filePath()); if (f.exists()) { f.open(QFile::ReadOnly); if (!tag->load(f)) { qWarning() << m_dirIterator << "is not a valid tag desktop file"; return false; } } return true; } QScopedPointer m_dirIterator; QString m_location; QString m_resourceType; KisTagSP m_tag; }; class FolderItem : public KisResourceStorage::ResourceItem { public: ~FolderItem() override {} }; class FolderIterator : public KisResourceStorage::ResourceIterator { public: FolderIterator(const QString &location, const QString &resourceType) : m_location(location) , m_resourceType(resourceType) { m_dirIterator.reset(new QDirIterator(location + '/' + resourceType, KisResourceLoaderRegistry::instance()->filters(resourceType), QDir::Files | QDir::Readable, QDirIterator::Subdirectories)); } ~FolderIterator() override {} bool hasNext() const override { return m_dirIterator->hasNext(); } void next() override { m_dirIterator->next(); } QString url() const override { return m_dirIterator->filePath(); } QString type() const override { return m_resourceType; } QDateTime lastModified() const override { return m_dirIterator->fileInfo().lastModified(); } KoResourceSP resource() const override { if (!loadResourceInternal()) { qWarning() << "Could not load resource" << m_dirIterator->filePath(); } return m_resource; } protected: bool loadResourceInternal() const { if (!m_resource || (m_resource && m_resource->filename() != m_dirIterator->filePath())) { QFile f(m_dirIterator->filePath()); f.open(QFile::ReadOnly); if (!m_resourceLoader) { const_cast(this)->m_resourceLoader = KisResourceLoaderRegistry::instance()->loader(m_resourceType, KisMimeDatabase::mimeTypeForFile(m_dirIterator->filePath())); } if (m_resourceLoader) { - const_cast(this)->m_resource = m_resourceLoader->load(m_dirIterator->filePath(), f, KisGlobalResourcesInterface::instance()); + const_cast(this)->m_resource = m_resourceLoader->load(m_dirIterator->fileName(), f, KisGlobalResourcesInterface::instance()); } else { qWarning() << "Could not get resource loader for type" << m_resourceType; } f.close(); } return !m_resource.isNull(); } KisResourceLoaderBase *m_resourceLoader {0}; KoResourceSP m_resource; QScopedPointer m_dirIterator; const QString m_location; const QString m_resourceType; }; KisFolderStorage::KisFolderStorage(const QString &location) : KisStoragePlugin(location) { } KisFolderStorage::~KisFolderStorage() { } bool KisFolderStorage::addTag(const QString &/*resourceType*/, KisTagSP /*tag*/) { return false; } bool KisFolderStorage::addResource(const QString &resourceType, KoResourceSP _resource) { // Find a new filename for the resource if it already exists: we do not rename old resources, but rename updated resources - QString fn = location() + "/" + resourceType + "/" + _resource->filename(); if (!QFileInfo(fn).exists()) { // Simply save it QFile f(fn); if (!f.open(QFile::WriteOnly)) { qWarning() << "Could not open resource file for writing" << fn; return false; } if (!_resource->saveToDevice(&f)) { qWarning() << "Could not save resource file" << fn; return false; } f.close(); } else { - QFileInfo fi(_resource->filename()); - - // Rename the old resource - KBackup::backupFile(fn); + QFileInfo fi(fn); // Save the new resource QString newFileName = fi.baseName() + - "_" + "." + QString("%1").arg(_resource->version() + 1, 4, 10, QChar('0')) + "." - + fi.completeSuffix(); + + fi.suffix(); QFile f(location() + "/" + resourceType + "/" + newFileName); + if (!f.open(QFile::WriteOnly)) { qWarning() << "Could not open resource file for writing" << fn; return false; } if (!_resource->saveToDevice(&f)) { qWarning() << "Could not save resource file" << fn; return false; } _resource->setVersion(_resource->version() + 1); _resource->setFilename(newFileName); f.close(); } return true; } KisResourceStorage::ResourceItem KisFolderStorage::resourceItem(const QString &url) { QFileInfo fi(url); FolderItem item; item.url = url; item.folder = fi.path().split("/").last(); item.lastModified = fi.lastModified(); return item; } KoResourceSP KisFolderStorage::resource(const QString &url) { QFileInfo fi(location() + '/' + url); const QString resourceType = fi.path().split("/").last(); KisResourceLoaderBase *loader = KisResourceLoaderRegistry::instance()->loader(resourceType, KisMimeDatabase::mimeTypeForFile(fi.absoluteFilePath(), false)); Q_ASSERT(loader); QFile f(fi.absoluteFilePath()); if (!f.open(QFile::ReadOnly)) { qWarning() << "Could not open" << fi.absoluteFilePath() << "for reading"; return 0; } - KoResourceSP res = loader->load(url, f, KisGlobalResourcesInterface::instance()); + + KoResourceSP res = loader->load(fi.fileName(), f, KisGlobalResourcesInterface::instance()); f.close(); return res; } QSharedPointer KisFolderStorage::resources(const QString &resourceType) { return QSharedPointer(new FolderIterator(location(), resourceType)); } QSharedPointer KisFolderStorage::tags(const QString &resourceType) { return QSharedPointer(new FolderTagIterator(location(), resourceType)); } + +QStringList KisFolderStorage::metaDataKeys() const +{ + return QStringList() << KisResourceStorage::s_meta_name; +} + +QVariant KisFolderStorage::metaData(const QString &key) const +{ + if (key == KisResourceStorage::s_meta_name) { + return i18n("Local Resources"); + } + return QVariant(); + +} diff --git a/libs/resources/KisFolderStorage.h b/libs/resources/KisFolderStorage.h index 4a609c117f..afb893e3aa 100644 --- a/libs/resources/KisFolderStorage.h +++ b/libs/resources/KisFolderStorage.h @@ -1,45 +1,55 @@ /* * Copyright (C) 2018 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 KISFOLDERSTORAGE_H #define KISFOLDERSTORAGE_H #include #include +/** + * KisFolderStorage is a KisStoragePlugin which handles resources + * stored in the user's resource folder. On initial startup, every + * resource that comes as a folder resource is copied to the user's + * resource folder. This is also the default location where the + * resources the user creates are stored. + */ class KRITARESOURCES_EXPORT KisFolderStorage : public KisStoragePlugin { public: KisFolderStorage(const QString &location); virtual ~KisFolderStorage(); /// Adds or updates this tag to the storage - bool addTag(const QString &resourceType, KisTagSP tag); + bool addTag(const QString &resourceType, KisTagSP tag) override; /// Adds or updates this resource to the storage - bool addResource(const QString &resourceType, KoResourceSP resource); + bool addResource(const QString &resourceType, KoResourceSP resource) override; KisResourceStorage::ResourceItem resourceItem(const QString &url) override; KoResourceSP resource(const QString &url) override; QSharedPointer resources(const QString &resourceType) override; QSharedPointer tags(const QString &resourceType) override; + + QStringList metaDataKeys() const override; + QVariant metaData(const QString &key) const override; }; #endif // KISFOLDERSTORAGE_H diff --git a/libs/resources/KisMemoryStorage.cpp b/libs/resources/KisMemoryStorage.cpp index 9f627c8d0d..7c31ff38f0 100644 --- a/libs/resources/KisMemoryStorage.cpp +++ b/libs/resources/KisMemoryStorage.cpp @@ -1,229 +1,249 @@ /* * Copyright (C) 2018 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 "KisMemoryStorage.h" #include #include #include #include #include #include #include class MemoryTagIterator : public KisResourceStorage::TagIterator { public: MemoryTagIterator(QVector tags, const QString &resourceType) : m_tags(tags) , m_resourceType(resourceType) { } bool hasNext() const override { return m_currentPosition < m_tags.size(); } void next() override { const_cast(this)->m_currentPosition += 1; } QString url() const override { return tag() ? tag()->url() : QString(); } QString name() const override { return tag() ? tag()->name() : QString(); } QString comment() const override {return tag() ? tag()->comment() : QString(); } KisTagSP tag() const override { if (m_currentPosition >= m_tags.size()) return 0; return m_tags.at(m_currentPosition); } private: int m_currentPosition {0}; QVector m_tags; QString m_resourceType; }; class MemoryItem : public KisResourceStorage::ResourceItem { public: ~MemoryItem() override {} }; class MemoryIterator : public KisResourceStorage::ResourceIterator { public: MemoryIterator(QVector resources, const QString &resourceType) : m_resources(resources) , m_resourceType(resourceType) { } ~MemoryIterator() override {} bool hasNext() const override { return m_currentPosition < m_resources.size(); } void next() override { const_cast(this)->m_currentPosition++; } QString url() const override { if (resource()) { return resource()->filename(); } return QString(); } QString type() const override { return m_resourceType; } QDateTime lastModified() const override { return QDateTime::fromMSecsSinceEpoch(0); } KoResourceSP resource() const override { if (m_currentPosition > m_resources.size()) return 0; return m_resources.at(m_currentPosition - 1); } private: int m_currentPosition {0}; QVector m_resources; QString m_resourceType; }; class KisMemoryStorage::Private { public: QHash > resources; QHash > tags; + QMap metadata; }; KisMemoryStorage::KisMemoryStorage(const QString &location) : KisStoragePlugin(location) , d(new Private) { } KisMemoryStorage::~KisMemoryStorage() { } KisMemoryStorage::KisMemoryStorage(const KisMemoryStorage &rhs) : KisStoragePlugin(rhs.location()) , d(new Private) { *this = rhs; } KisMemoryStorage &KisMemoryStorage::operator=(const KisMemoryStorage &rhs) { if (this != &rhs) { Q_FOREACH(const QString &key, rhs.d->resources.keys()) { Q_FOREACH(const KoResourceSP resource, rhs.d->resources[key]) { if (!d->resources.contains(key)) { d->resources[key] = QVector(); } d->resources[key] << resource->clone(); } } Q_FOREACH(const QString &key, rhs.d->tags.keys()) { Q_FOREACH(const KisTagSP tag, rhs.d->tags[key]) { if (!d->tags.contains(key)) { d->tags[key] = QVector(); } d->tags[key] << tag->clone(); } } } return *this; } bool KisMemoryStorage::addTag(const QString &resourceType, KisTagSP tag) { if (!d->tags.contains(resourceType)) { d->tags[resourceType] = QVector(); } if (!d->tags[resourceType].contains(tag)) { d->tags[resourceType].append(tag); } return true; } bool KisMemoryStorage::addResource(const QString &resourceType, KoResourceSP resource) { if (!d->resources.contains(resourceType)) { d->resources[resourceType] = QVector(); } if (!d->resources[resourceType].contains(resource)) { d->resources[resourceType].append(resource); } return true; } KisResourceStorage::ResourceItem KisMemoryStorage::resourceItem(const QString &url) { MemoryItem item; item.url = url; item.folder = QString(); item.lastModified = QDateTime::fromMSecsSinceEpoch(0); return item; } KoResourceSP KisMemoryStorage::resource(const QString &url) { KoResourceSP resource; QFileInfo fi(location() + '/' + url); const QString resourceType = fi.path().split("/").last(); Q_FOREACH(resource, d->resources[resourceType]) { if (resource->filename() == url) { break; } } return resource; } QSharedPointer KisMemoryStorage::resources(const QString &resourceType) { return QSharedPointer(new MemoryIterator(d->resources[resourceType], resourceType)); } QSharedPointer KisMemoryStorage::tags(const QString &resourceType) { return QSharedPointer(new MemoryTagIterator(d->tags[resourceType], resourceType)); } + +void KisMemoryStorage::setMetaData(const QString &key, const QVariant &value) +{ + d->metadata[key] = value; +} + +QStringList KisMemoryStorage::metaDataKeys() const +{ + return QStringList() << KisResourceStorage::s_meta_name; +} + +QVariant KisMemoryStorage::metaData(const QString &key) const +{ + QVariant r; + if (d->metadata.contains(key)) { + r = d->metadata[key]; + } + return r; +} diff --git a/libs/resources/KisMemoryStorage.h b/libs/resources/KisMemoryStorage.h index ef4c935937..fe820ea203 100644 --- a/libs/resources/KisMemoryStorage.h +++ b/libs/resources/KisMemoryStorage.h @@ -1,58 +1,65 @@ /* * Copyright (C) 2018 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 KISMEMORYSTORAGE_H #define KISMEMORYSTORAGE_H #include #include /** * @brief The KisMemoryStorage class stores the temporary resources - * that are not saved to disk or bundle. + * that are not saved to disk or bundle. It is also used to stores + * transient per-document resources, such as the document-local palette + * list. */ class KRITARESOURCES_EXPORT KisMemoryStorage : public KisStoragePlugin { public: KisMemoryStorage(const QString &location = QString("memory")); virtual ~KisMemoryStorage(); /// Copying the memory storage clones all contained resources and tags KisMemoryStorage(const KisMemoryStorage &rhs); /// This clones all contained resources and tags from rhs KisMemoryStorage &operator=(const KisMemoryStorage &rhs); - bool addTag(const QString &resourceType, KisTagSP tag); - bool addResource(const QString &resourceType, KoResourceSP resource); + bool addTag(const QString &resourceType, KisTagSP tag) override; + bool addResource(const QString &resourceType, KoResourceSP resource) override; KisResourceStorage::ResourceItem resourceItem(const QString &url) override; KoResourceSP resource(const QString &url) override; QSharedPointer resources(const QString &resourceType) override; QSharedPointer tags(const QString &resourceType) override; + void setMetaData(const QString &key, const QVariant &value) override; + QStringList metaDataKeys() const override; + QVariant metaData(const QString &key) const override; + + private: class Private; QScopedPointer d; }; #endif // KISMEMORYSTORAGE_H diff --git a/libs/resources/KisResourceCacheDb.cpp b/libs/resources/KisResourceCacheDb.cpp index f7b56b3735..cf8753d02b 100644 --- a/libs/resources/KisResourceCacheDb.cpp +++ b/libs/resources/KisResourceCacheDb.cpp @@ -1,1286 +1,1284 @@ /* * Copyright (C) 2018 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 "KisResourceCacheDb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceLocator.h" #include "KisResourceCacheDb.h" #include "KisResourceLoaderRegistry.h" const QString dbDriver = "QSQLITE"; const QString KisResourceCacheDb::dbLocationKey { "ResourceCacheDbDirectory" }; const QString KisResourceCacheDb::resourceCacheDbFilename { "resourcecache.sqlite" }; const QString KisResourceCacheDb::databaseVersion { "0.0.2" }; QStringList KisResourceCacheDb::storageTypes { QStringList() }; QStringList KisResourceCacheDb::disabledBundles { QStringList() << "Krita_3_Default_Resources.bundle" }; bool KisResourceCacheDb::s_valid {false}; QString KisResourceCacheDb::s_lastError {QString()}; bool KisResourceCacheDb::isValid() { return s_valid; } QString KisResourceCacheDb::lastError() { return s_lastError; } QSqlError createDatabase(const QString &location) { // NOTE: if the id's of Unknown and Memory in the database // will change, and that will break the queries that // remove Unknown and Memory storages on start-up. KisResourceCacheDb::storageTypes << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(1)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(2)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(3)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(4)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(5)) << KisResourceStorage::storageTypeToString(KisResourceStorage::StorageType(6)) ; if (!QSqlDatabase::connectionNames().isEmpty()) { infoResources << "Already connected to resource cache database"; return QSqlError(); } QDir dbLocation(location); if (!dbLocation.exists()) { dbLocation.mkpath(dbLocation.path()); } QSqlDatabase db = QSqlDatabase::addDatabase(dbDriver); db.setDatabaseName(location + "/" + KisResourceCacheDb::resourceCacheDbFilename); //qDebug() << "QuerySize supported" << db.driver()->hasFeature(QSqlDriver::QuerySize); if (!db.open()) { qWarning() << "Could not connect to resource cache database"; return db.lastError(); } QStringList tables = QStringList() << "version_information" << "storage_types" << "resource_types" << "storages" << "tags" << "resources" << "versioned_resources" << "resource_tags" << "metadata" << "tags_storages"; QStringList dbTables; // Verify whether we should recreate the database { bool allTablesPresent = true; dbTables = db.tables(); Q_FOREACH(const QString &table, tables) { if (!dbTables.contains(table)) { allTablesPresent = false; } } bool schemaIsOutDated = false; QString schemaVersion = "Unknown"; QString kritaVersion = "Unknown"; int creationDate = 0; if (dbTables.contains("version_information")) { // Verify the version number QFile f(":/get_version_information.sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q(f.readAll()); if (q.size() > 0) { q.first(); schemaVersion = q.value(0).toString(); kritaVersion = q.value(1).toString(); creationDate = q.value(2).toInt(); if (schemaVersion != KisResourceCacheDb::databaseVersion) { // XXX: Implement migration schemaIsOutDated = true; qFatal("Database schema is outdated, migration is needed. Database migration has NOT been implemented yet."); } } } else { return QSqlError("Error executing SQL", "Could not open get_version_information.sql", QSqlError::StatementError); } } if (allTablesPresent && !schemaIsOutDated) { KisUsageLogger::log(QString("Database is up to date. Version: %1, created by Krita %2, at %3") .arg(schemaVersion) .arg(kritaVersion) .arg(QDateTime::fromSecsSinceEpoch(creationDate).toString())); return QSqlError(); } } // Create tables Q_FOREACH(const QString &table, tables) { QFile f(":/create_" + table + ".sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q; if (!q.exec(f.readAll())) { qWarning() << "Could not create table" << table << q.lastError(); return db.lastError(); } infoResources << "Created table" << table; } else { return QSqlError("Error executing SQL", QString("Could not find SQL file %1").arg(table), QSqlError::StatementError); } } // Create indexes QStringList indexes = QStringList() << "storages" << "versioned_resources"; Q_FOREACH(const QString &index, indexes) { QFile f(":/create_index_" + index + ".sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q; if (!q.exec(f.readAll())) { qWarning() << "Could not create index" << index; return db.lastError(); } infoResources << "Created table" << index; } else { return QSqlError("Error executing SQL", QString("Could not find SQL file %1").arg(index), QSqlError::StatementError); } } // Fill lookup tables { if (dbTables.contains("storage_types")) { QSqlQuery q; if (!q.exec("DELETE * FROM storage_types;")) { qWarning() << "Could not clear table storage_types" << db.lastError(); } } QFile f(":/fill_storage_types.sql"); if (f.open(QFile::ReadOnly)) { QString sql = f.readAll(); Q_FOREACH(const QString &originType, KisResourceCacheDb::storageTypes) { QSqlQuery q(sql); q.addBindValue(originType); if (!q.exec()) { qWarning() << "Could not insert" << originType << db.lastError() << q.executedQuery(); return db.lastError(); } } infoResources << "Filled lookup table storage_types"; } else { return QSqlError("Error executing SQL", QString("Could not find SQL fill_storage_types.sql."), QSqlError::StatementError); } } { if (dbTables.contains("resource_types")) { QSqlQuery q; if (!q.exec("DELETE * FROM resource_types;")) { qWarning() << "Could not clear table resource_types" << db.lastError(); } } QFile f(":/fill_resource_types.sql"); if (f.open(QFile::ReadOnly)) { QString sql = f.readAll(); Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) { QSqlQuery q(sql); q.addBindValue(resourceType); if (!q.exec()) { qWarning() << "Could not insert" << resourceType << db.lastError() << q.executedQuery(); return db.lastError(); } } infoResources << "Filled lookup table resource_types"; } else { return QSqlError("Error executing SQL", QString("Could not find SQL fill_resource_types.sql."), QSqlError::StatementError); } } { QFile f(":/fill_version_information.sql"); if (f.open(QFile::ReadOnly)) { QString sql = f.readAll(); QSqlQuery q; q.prepare(sql); q.addBindValue(KisResourceCacheDb::databaseVersion); q.addBindValue(KritaVersionWrapper::versionString()); q.addBindValue(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); if (!q.exec()) { qWarning() << "Could not insert the current version" << db.lastError() << q.executedQuery() << q.boundValues(); return db.lastError(); } infoResources << "Filled version table"; } else { return QSqlError("Error executing SQL", QString("Could not find SQL fill_version_information.sql."), QSqlError::StatementError); } } return QSqlError(); } bool KisResourceCacheDb::initialize(const QString &location) { QSqlError err = createDatabase(location); s_valid = !err.isValid(); switch (err.type()) { case QSqlError::NoError: s_lastError = QString(); break; case QSqlError::ConnectionError: s_lastError = QString("Could not initialize the resource cache database. Connection error: %1").arg(err.text()); break; case QSqlError::StatementError: s_lastError = QString("Could not initialize the resource cache database. Statement error: %1").arg(err.text()); break; case QSqlError::TransactionError: s_lastError = QString("Could not initialize the resource cache database. Transaction error: %1").arg(err.text()); break; case QSqlError::UnknownError: s_lastError = QString("Could not initialize the resource cache database. Unknown error: %1").arg(err.text()); break; } // Delete all storages that are no longer known to the resource locator (including the memory storages) deleteTemporaryResources(); return s_valid; } int KisResourceCacheDb::resourceIdForResource(const QString &resourceName, const QString &resourceType, const QString &storageLocation) { QFile f(":/select_resource_id.sql"); f.open(QFile::ReadOnly); QSqlQuery q; if (!q.prepare(f.readAll())) { qWarning() << "Could not read and prepare resourceIdForResource" << q.lastError(); return -1; } q.bindValue(":name", resourceName); q.bindValue(":resource_type", resourceType); q.bindValue(":storage_location", storageLocation); if (!q.exec()) { qWarning() << "Could not query resourceIdForResource" << q.boundValues() << q.lastError(); return -1; } if (!q.first()) { return -1; } return q.value(0).toInt(); } bool KisResourceCacheDb::resourceNeedsUpdating(int resourceId, QDateTime timestamp) { QSqlQuery q; if (!q.prepare("SELECT timestamp\n" "FROM versioned_resources\n" "WHERE resource_id = :resource_id\n" "AND version = (SELECT MAX(version)\n" " FROM versioned_resources\n" " WHERE resource_id = :resource_id);")) { qWarning() << "Could not prepare resourceNeedsUpdating statement" << q.lastError(); return false; } q.bindValue(":resource_id", resourceId); if (!q.exec()) { qWarning() << "Could not query for the most recent timestamp" << q.boundValues() << q.lastError(); return false; } if (!q.first()) { qWarning() << "Inconsistent database: could not find a version for resource with Id" << resourceId; return false; } QVariant resourceTimeStamp = q.value(0); if (!resourceTimeStamp.isValid()) { qWarning() << "Could not retrieve timestamp from versioned_resources" << resourceId; return false; } return (timestamp.toSecsSinceEpoch() > resourceTimeStamp.toInt()); } bool KisResourceCacheDb::addResourceVersion(int resourceId, QDateTime timestamp, KisResourceStorageSP storage, KoResourceSP resource) { bool r = false; - // Create the new version. The resource is expected to have an updated version number, or // this will fail on the unique index on resource_id, storage_id and version. { QSqlQuery q; r = q.prepare("INSERT INTO versioned_resources \n" "(resource_id, storage_id, version, location, timestamp, md5sum)\n" "VALUES\n" "( :resource_id\n" ", (SELECT id \n" " FROM storages \n" " WHERE location = :storage_location)\n" ", :version\n" ", :location\n" ", :timestamp\n" ", :md5sum\n" ");"); if (!r) { qWarning() << "Could not prepare addResourceVersion statement" << q.lastError(); return r; } q.bindValue(":resource_id", resourceId); q.bindValue(":storage_location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); q.bindValue(":version", resource->version()); q.bindValue(":location", QFileInfo(resource->filename()).fileName()); q.bindValue(":timestamp", timestamp.toSecsSinceEpoch()); Q_ASSERT(!resource->md5().isEmpty()); q.bindValue(":md5sum", resource->md5().toHex()); r = q.exec(); if (!r) { qWarning() << "Could not execute addResourceVersion statement" << q.boundValues() << q.lastError(); return r; } } // Update the resource itself. The resource gets a new filename when it's updated { QSqlQuery q; r = q.prepare("UPDATE resources\n" "SET name = :name\n" ", filename = :filename\n" ", tooltip = :tooltip\n" ", thumbnail = :thumbnail\n" ", version = :version\n" "WHERE id = :id"); if (!r) { qWarning() << "Could not prepare updateResource statement" << q.lastError(); return r; } q.bindValue(":name", resource->name()); q.bindValue(":filename", QFileInfo(resource->filename()).fileName()); q.bindValue(":tooltip", i18n(resource->name().toUtf8())); q.bindValue(":version", resource->version()); QByteArray ba; QBuffer buf(&ba); buf.open(QBuffer::WriteOnly); - resource->image().save(&buf, "PNG"); + resource->thumbnail().save(&buf, "PNG"); buf.close(); q.bindValue(":thumbnail", ba); q.bindValue(":id", resourceId); r = q.exec(); if (!r) { qWarning() << "Could not update resource" << q.boundValues() << q.lastError(); } } - return r; } bool KisResourceCacheDb::addResource(KisResourceStorageSP storage, QDateTime timestamp, KoResourceSP resource, const QString &resourceType) { bool r = false; if (!s_valid) { qWarning() << "KisResourceCacheDb::addResource: The database is not valid"; return false; } if (!resource || !resource->valid()) { qWarning() << "KisResourceCacheDb::addResource: The resource is not valid"; // We don't care about invalid resources and will just ignore them. return true; } bool temporary = (storage->type() == KisResourceStorage::StorageType::Memory); // Check whether it already exists int resourceId = resourceIdForResource(resource->name(), resourceType, KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (resourceId > -1) { if (resourceNeedsUpdating(resourceId, timestamp)) { r = addResourceVersion(resourceId, timestamp, storage, resource); } return true; } QSqlQuery q; r = q.prepare("INSERT INTO resources \n" "(storage_id, resource_type_id, name, filename, tooltip, thumbnail, status, temporary) \n" "VALUES \n" "((SELECT id " " FROM storages " " WHERE location = :storage_location)\n" ", (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type)\n" ", :name\n" ", :filename\n" ", :tooltip\n" ", :thumbnail\n" ", :status\n" ", :temporary);"); if (!r) { qWarning() << "Could not prepare addResource statement" << q.lastError(); return r; } q.bindValue(":storage_location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); q.bindValue(":resource_type", resourceType); q.bindValue(":name", resource->name()); q.bindValue(":filename", QFileInfo(resource->filename()).fileName()); q.bindValue(":tooltip", i18n(resource->name().toUtf8())); QByteArray ba; QBuffer buf(&ba); buf.open(QBuffer::WriteOnly); resource->image().save(&buf, "PNG"); buf.close(); q.bindValue(":thumbnail", ba); q.bindValue(":status", 1); q.bindValue(":temporary", (temporary ? 1 : 0)); r = q.exec(); if (!r) { qWarning() << "Could not execute addResource statement" << q.boundValues() << q.lastError(); return r; } resourceId = resourceIdForResource(resource->name(), resourceType, KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); // Then add a new version r = q.prepare("INSERT INTO versioned_resources\n" "(resource_id, storage_id, version, location, timestamp, md5sum)\n" "VALUES\n" "(:resource_id\n" ", (SELECT id FROM storages\n" " WHERE location = :storage_location)\n" ", :version\n" ", :location\n" ", :timestamp\n" ", :md5sum\n" ");"); if (!r) { qWarning() << "Could not prepare intitial addResourceVersion statement" << q.lastError(); return r; } q.bindValue(":resource_id", resourceId); q.bindValue(":storage_location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); q.bindValue(":version", resource->version()); q.bindValue(":location", QFileInfo(resource->filename()).fileName()); q.bindValue(":timestamp", timestamp.toSecsSinceEpoch()); //Q_ASSERT(!resource->md5().isEmpty()); if (resource->md5().isEmpty()) { qWarning() << "No md5 for resource" << resource->name() << resourceType << storage->location(); } q.bindValue(":md5sum", resource->md5().toHex()); r = q.exec(); if (!r) { qWarning() << "Could not execute initial addResourceVersion statement" << q.boundValues() << q.lastError(); } return r; } bool KisResourceCacheDb::addResources(KisResourceStorageSP storage, QString resourceType) { QSqlDatabase::database().transaction(); QSharedPointer iter = storage->resources(resourceType); while (iter->hasNext()) { iter->next(); KoResourceSP resource = iter->resource(); if (resource && resource->valid()) { if (!addResource(storage, iter->lastModified(), resource, iter->type())) { qWarning() << "Could not add resource" << QFileInfo(resource->filename()).fileName() << "to the database"; } } } QSqlDatabase::database().commit(); return true; } bool KisResourceCacheDb::removeResource(int resourceId) { if (resourceId < 0) { qWarning() << "Invalid resource id; cannot remove resource"; return false; } QSqlQuery q; bool r = q.prepare("UPDATE resources\n" "SET status = 0\n" "WHERE id = :resource_id"); if (!r) { qWarning() << "Could not prepare removeResource query" << q.lastError(); } q.bindValue(":resource_id", resourceId); if (!q.exec()) { qWarning() << "Could not update resource" << resourceId << "to inactive" << q.lastError(); return false; } return true; } bool KisResourceCacheDb::tagResource(KisResourceStorageSP storage, const QString resourceName, KisTagSP tag, const QString &resourceType) { // Get resource id int resourceId = resourceIdForResource(resourceName, resourceType, KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (resourceId < 0) { qWarning() << "Could not find resource to tag" << KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()) << resourceName << resourceType; return false; } // Get tag id int tagId {-1}; { QFile f(":/select_tag.sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q; if (!q.prepare(f.readAll())) { qWarning() << "Could not read and prepare select_tag.sql" << q.lastError(); return false; } q.bindValue(":url", tag->url()); q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not query tags" << q.boundValues() << q.lastError(); return false; } if (!q.first()) { qWarning() << "Could not find tag" << q.boundValues() << q.lastError(); return false; } tagId = q.value(0).toInt(); } } QSqlQuery q; if (!q.prepare("INSERT INTO resource_tags\n" "(resource_id, tag_id)\n" "VALUES\n" "(:resource_id, :tag_id);")) { qWarning() << "Could not prepare tagResource statement" << q.lastError(); return false; } q.bindValue(":resource_id", resourceId); q.bindValue(":tag_id", tagId); if (!q.exec()) { qWarning() << "Could not execute tagResource stagement" << q.boundValues() << q.lastError(); return false; } return true; } bool KisResourceCacheDb::hasTag(const QString &url, const QString &resourceType) { QFile f(":/select_tag.sql"); if (f.open(QFile::ReadOnly)) { QSqlQuery q; if (!q.prepare(f.readAll())) { qWarning() << "Could not read and prepare select_tag.sql" << q.lastError(); return false; } q.bindValue(":url", url); q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not query tags" << q.boundValues() << q.lastError(); } return q.first(); } qWarning() << "Could not open select_tag.sql"; return false; } bool KisResourceCacheDb::linkTagToStorage(const QString &url, const QString &resourceType, const QString &storageLocation) { QSqlQuery q; if (!q.prepare("INSERT INTO tags_storages\n" "(tag_id, storage_id)\n" "VALUES\n" "(\n" " ( SELECT id\n" " FROM tags\n" " WHERE url = :url\n" " AND resource_type_id = (SELECT id \n" " FROM resource_types\n" " WHERE name = :resource_type)" " )\n" ",( SELECT id\n" " FROM storages\n" " WHERE location = :storage_location\n" " )\n" ");")) { qWarning() << "Could not prepare add tag/storage statement" << q.lastError(); return false; } q.bindValue(":url", url); q.bindValue(":resource_type", resourceType); q.bindValue(":storage_location", KisResourceLocator::instance()->makeStorageLocationRelative(storageLocation)); if (!q.exec()) { qWarning() << "Could not insert tag/storage link" << q.boundValues() << q.lastError(); return false; } return true; } bool KisResourceCacheDb::addTag(const QString &resourceType, const QString storageLocation, const QString url, const QString name, const QString comment) { if (hasTag(url, resourceType)) { // Check whether this storage is already registered for this tag QSqlQuery q; if (!q.prepare("SELECT storages.location\n" "FROM tags_storages\n" ", tags\n" ", storages\n" "WHERE tags.id = tags_storages.tag_id\n" "AND storages.id = tags_storages.storage_id\n" "AND tags.resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type)\n" "AND tags.url = :url")) { qWarning() << "Could not prepare select tags from tags_storages query" << q.lastError(); } q.bindValue(":url", url); q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not execute tags_storages query" << q.boundValues() << q.lastError(); } // If this tag is not yet linked to the storage, link it if (!q.first()) { return linkTagToStorage(url, resourceType, storageLocation); } return true; } // Insert the tag { QSqlQuery q; if (!q.prepare("INSERT INTO tags\n" "(url, name, comment, resource_type_id, active)\n" "VALUES\n" "( :url\n" ", :name\n" ", :comment\n" ", (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type)\n" ", 1" ");")) { qWarning() << "Could not prepare insert tag statement" << q.lastError(); return false; } q.bindValue(":url", url); q.bindValue(":name", name); q.bindValue(":comment", comment); q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not insert tag" << q.boundValues() << q.lastError(); } } linkTagToStorage(url, resourceType, storageLocation); return true; } bool KisResourceCacheDb::addTags(KisResourceStorageSP storage, QString resourceType) { QSqlDatabase::database().transaction(); QSharedPointer iter = storage->tags(resourceType); while(iter->hasNext()) { iter->next(); if (!addTag(resourceType, storage->location(), iter->url(), iter->name(), iter->comment())) { qWarning() << "Could not add tag" << iter->url() << "to the database"; } if (!iter->tag()->defaultResources().isEmpty()) { Q_FOREACH(const QString &resourceName, iter->tag()->defaultResources()) { if (!tagResource(storage, resourceName, iter->tag(), resourceType)) { qWarning() << "Could not tag resource" << resourceName << "with tag" << iter->url(); } } } } QSqlDatabase::database().commit(); return true; } bool KisResourceCacheDb::addStorage(KisResourceStorageSP storage, bool preinstalled) { bool r = true; if (!s_valid) { qWarning() << "The database is not valid"; return false; } { QSqlQuery q; r = q.prepare("SELECT * FROM storages WHERE location = :location"); q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); r = q.exec(); if (!r) { qWarning() << "Could not select from storages"; return r; } if (q.first()) { qDebug() << "Storage already exists" << storage; return true; } } // Insert the storage; { QSqlQuery q; r = q.prepare("INSERT INTO storages\n " "(storage_type_id, location, timestamp, pre_installed, active, thumbnail)\n" "VALUES\n" "(:storage_type_id, :location, :timestamp, :pre_installed, :active, :thumbnail);"); if (!r) { qWarning() << "Could not prepare query" << q.lastError(); return r; } q.bindValue(":storage_type_id", static_cast(storage->type())); q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); q.bindValue(":timestamp", storage->timestamp().toSecsSinceEpoch()); q.bindValue(":pre_installed", preinstalled ? 1 : 0); q.bindValue(":active", !disabledBundles.contains(storage->name())); QByteArray ba; QBuffer buf(&ba); buf.open(QBuffer::WriteOnly); storage->thumbnail().save(&buf, "PNG"); buf.close(); q.bindValue(":thumbnail", ba); r = q.exec(); if (!r) qWarning() << "Could not execute query" << q.lastError(); } // Insert the metadata { QStringList keys = storage->metaDataKeys(); if (keys.size() > 0) { QSqlQuery q; if (!q.prepare("SELECT MAX(id)\n" "FROM storages\n")) { qWarning() << "Could not create select storages query for metadata" << q.lastError(); } if (!q.exec()) { qWarning() << "Could not execute select storages query for metadata" << q.lastError(); } q.first(); int id = q.value(0).toInt(); QMap metadata; Q_FOREACH(const QString &key, storage->metaDataKeys()) { metadata[key] = storage->metaData(key); } addMetaDataForId(metadata, id, "storages"); } } Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) { if (!KisResourceCacheDb::addResources(storage, resourceType)) { qWarning() << "Failed to add all resources for storage" << storage; r = false; } if (!KisResourceCacheDb::addTags(storage, resourceType)) { qWarning() << "Failed to add all tags for storage" << storage; } } return r; } bool KisResourceCacheDb::deleteStorage(KisResourceStorageSP storage) { { QSqlQuery q; if (!q.prepare("DELETE FROM resources\n" "WHERE id IN (SELECT versioned_resources.resource_id\n" " FROM versioned_resources\n" " WHERE versioned_resources.storage_id = (SELECT storages.id\n" " FROM storages\n" " WHERE storages.location = :location)\n" " );")) { qWarning() << "Could not prepare delete resources query in deleteStorage" << q.lastError(); return false; } q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (!q.exec()) { qWarning() << "Could not execute delete resources query in deleteStorage" << q.lastError(); return false; } } { QSqlQuery q; if (!q.prepare("DELETE FROM versioned_resources\n" "WHERE storage_id = (SELECT storages.id\n" " FROM storages\n" " WHERE storages.location = :location);")) { qWarning() << "Could not prepare delete versioned_resources query" << q.lastError(); return false; } q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (!q.exec()) { qWarning() << "Could not execute delete versioned_resources query" << q.lastError(); return false; } } { QSqlQuery q; if (!q.prepare("DELETE FROM storages\n" "WHERE location = :location;")) { qWarning() << "Could not prepare delete storages query" << q.lastError(); return false; } q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (!q.exec()) { qWarning() << "Could not execute delete storages query" << q.lastError(); return false; } } return true; } bool KisResourceCacheDb::synchronizeStorage(KisResourceStorageSP storage) { qDebug() << "Going to synchronize" << storage->location(); QTime t; t.start(); QSqlDatabase::database().transaction(); if (!s_valid) { qWarning() << "KisResourceCacheDb::addResource: The database is not valid"; return false; } bool success = true; // Find the storage in the database QSqlQuery q; if (!q.prepare("SELECT id\n" ", timestamp\n" ", pre_installed\n" "FROM storages\n" "WHERE location = :location\n")) { qWarning() << "Could not prepare storage timestamp statement" << q.lastError(); } q.bindValue(":location", KisResourceLocator::instance()->makeStorageLocationRelative(storage->location())); if (!q.exec()) { qWarning() << "Could not execute storage timestamp statement" << q.boundValues() << q.lastError(); } if (!q.first()) { // This is a new storage, the user must have dropped it in the path before restarting Krita, so add it. qDebug() << "Adding storage to the database:" << storage; if (!addStorage(storage, false)) { qWarning() << "Could not add new storage" << storage->name() << "to the database"; success = false; } return true; } // Only check the time stamp for container storages, not the contents if (storage->type() != KisResourceStorage::StorageType::Folder) { qDebug() << storage->location() << "is not a folder, going to check timestamps. Database:" << q.value(1).toInt() << ", File:" << storage->timestamp().toSecsSinceEpoch(); if (!q.value(0).isValid()) { qWarning() << "Could not retrieve timestamp for storage" << KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()); success = false; } if (storage->timestamp().toSecsSinceEpoch() > q.value(1).toInt()) { qDebug() << "Deleting" << storage->location() << "because the one on disk is newer."; if (!deleteStorage(storage)) { qWarning() << "Could not delete storage" << KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()); success = false; } qDebug() << "Inserting" << storage->location(); if (!addStorage(storage, q.value(2).toBool())) { qWarning() << "Could not add storage" << KisResourceLocator::instance()->makeStorageLocationRelative(storage->location()); success = false; } } } else { // This is a folder, we need to check what's on disk and what's in the database // Check whether everything in the storage is in the database QList resourcesToBeDeleted; Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) { QStringList resourcesOnDisk; // Check the folder QSharedPointer iter = storage->resources(resourceType); while (iter->hasNext()) { iter->next(); qDebug() << "\tadding resources" << iter->url(); KoResourceSP resource = iter->resource(); resourcesOnDisk << QFileInfo(iter->url()).fileName(); if (resource) { if (!addResource(storage, iter->lastModified(), resource, iter->type())) { qWarning() << "Could not add/update resource" << QFileInfo(resource->filename()).fileName() << "to the database"; success = false; } } } qDebug() << "Checking for" << resourceType << ":" << resourcesOnDisk; QSqlQuery q; q.setForwardOnly(true); if (!q.prepare("SELECT resources.id, resources.filename\n" "FROM resources\n" ", resource_types\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n" "AND storage_id in (SELECT id\n" " FROM storages\n" " WHERE storage_type_id == :storage_type)")) { qWarning() << "Could not prepare resource by type query" << q.lastError(); success = false; continue; } q.bindValue(":resource_type", resourceType); q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Folder); if (!q.exec()) { qWarning() << "Could not exec resource by type query" << q.boundValues() << q.lastError(); success = false; continue; } while (q.next()) { if (!resourcesOnDisk.contains(q.value(1).toString())) { resourcesToBeDeleted << q.value(0).toInt(); } } } QSqlQuery deleteResources; if (!deleteResources.prepare("DELETE FROM resources WHERE id = :id")) { success = false; qWarning() << "Could not prepare delete Resources query"; } QSqlQuery deleteResourceVersions; if (!deleteResourceVersions.prepare("DELETE FROM versioned_resources WHERE resource_id = :id")) { success = false; qWarning() << "Could not prepare delete Resources query"; } Q_FOREACH(int id, resourcesToBeDeleted) { deleteResourceVersions.bindValue(":id", id); if (!deleteResourceVersions.exec()) { success = false; qWarning() << "Could not delete resource version" << deleteResourceVersions.boundValues() << deleteResourceVersions.lastError(); } deleteResources.bindValue(":id", id); if (!deleteResources.exec()) { success = false; qWarning() << "Could not delete resource" << deleteResources.boundValues() << deleteResources.lastError(); } } } QSqlDatabase::database().commit(); qDebug() << "Synchronizing the storages took" << t.msec() << "milliseconds for" << storage->location(); return success; } void KisResourceCacheDb::deleteTemporaryResources() { QSqlDatabase::database().transaction(); QSqlQuery q; if (!q.prepare("DELETE FROM versioned_resources\n" "WHERE storage_id in (SELECT id\n" " FROM storages\n" " WHERE storage_type_id == :storage_type)")) { qWarning() << "Could not prepare delete versioned resources from Unknown or Memory storages query." << q.lastError(); } q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Memory); if (!q.exec()) { qWarning() << "Could not execute delete versioned resources from Unknown or Memory storages query." << q.lastError(); } if (!q.prepare("DELETE FROM resources\n" "WHERE storage_id in (SELECT id\n" " FROM storages\n" " WHERE storage_type_id == :storage_type)")) { qWarning() << "Could not prepare delete resources from Unknown or Memory storages query." << q.lastError(); } q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Memory); if (!q.exec()) { qWarning() << "Could not execute delete resources from Unknown or Memory storages query." << q.lastError(); } if (!q.prepare("DELETE FROM versioned_resources\n" "WHERE resource_id IN (SELECT id FROM resources\n" " WHERE temporary = 1)")) { qWarning() << "Could not prepare delete temporary versioned resources query." << q.lastError(); } if (!q.exec()) { qWarning() << "Could not execute delete temporary versioned resources query." << q.lastError(); } if (!q.prepare("DELETE FROM resources\n" "WHERE temporary = 1")) { qWarning() << "Could not prepare delete temporary resources query." << q.lastError(); return; } if (!q.exec()) { qWarning() << "Could not execute delete temporary resources query." << q.lastError(); } if (!q.prepare("DELETE FROM storages\n" "WHERE storage_type_id == :storage_type\n")) { qWarning() << "Could not prepare delete Unknown or Memory storages query." << q.lastError(); } q.bindValue(":storage_type", (int)KisResourceStorage::StorageType::Memory); if (!q.exec()) { qWarning() << "Could not execute delete Unknown or Memory storages query." << q.lastError(); } QSqlDatabase::database().commit(); } bool KisResourceCacheDb::registerResourceType(const QString &resourceType) { // Check whether the type already exists { QSqlQuery q; if (!q.prepare("SELECT count(*)\n" "FROM resource_types\n" "WHERE name = :resource_type\n")) { qWarning() << "Could not prepare select from resource_types query" << q.lastError(); return false; } q.bindValue(":resource_type", resourceType); if (!q.exec()) { qWarning() << "Could not execute select from resource_types query" << q.lastError(); return false; } q.first(); int rowCount = q.value(0).toInt(); if (rowCount > 0) { return true; } } // if not, add it QFile f(":/fill_resource_types.sql"); if (f.open(QFile::ReadOnly)) { QString sql = f.readAll(); QSqlQuery q(sql); q.addBindValue(resourceType); if (!q.exec()) { qWarning() << "Could not insert" << resourceType << q.lastError(); return false; } return true; } qWarning() << "Could not open fill_resource_types.sql"; return false; } QMap KisResourceCacheDb::metaDataForId(int id, const QString &tableName) { QMap map; QSqlQuery q; q.setForwardOnly(true); if (!q.prepare("SELECT key\n" ", value\n" "FROM metadata\n" "WHERE foreign_id = :id\n" "AND table_name = :table")) { qWarning() << "Could not prepare metadata query" << q.lastError(); return map; } q.bindValue(":id", id); q.bindValue(":table", tableName); if (!q.exec()) { qWarning() << "Could not execute metadata query" << q.lastError(); return map; } while (q.next()) { QString key = q.value(0).toString(); QByteArray ba = q.value(1).toByteArray(); QDataStream ds(QByteArray::fromBase64(ba)); QVariant value; ds >> value; map[key] = value; } return map; } bool KisResourceCacheDb::updateMetaDataForId(const QMap map, int id, const QString &tableName) { QSqlDatabase::database().transaction(); { QSqlQuery q; if (!q.prepare("DELETE FROM metadata\n" "WHERE foreign_id = :id\n" "AND table_name = :table\n")) { qWarning() << "Could not prepare delete metadata query" << q.lastError(); return false; } q.bindValue(":id", id); q.bindValue(":table", tableName); if (!q.exec()) { QSqlDatabase::database().rollback(); qWarning() << "Could not execute delete metadata query" << q.lastError(); return false; } } if (addMetaDataForId(map, id, tableName)) { QSqlDatabase::database().commit(); } else { QSqlDatabase::database().rollback(); } return true; } bool KisResourceCacheDb::addMetaDataForId(const QMap map, int id, const QString &tableName) { QSqlQuery q; if (!q.prepare("INSERT INTO metadata\n" "(foreign_id, table_name, key, value)\n" "VALUES\n" "(:id, :table, :key, :value)")) { QSqlDatabase::database().rollback(); qWarning() << "Could not create insert metadata query" << q.lastError(); return false; } QMap::const_iterator iter = map.cbegin(); while (iter != map.cend()) { q.bindValue(":id", id); q.bindValue(":table", tableName); q.bindValue(":key", iter.key()); QVariant v = iter.value(); QByteArray ba; QDataStream ds(&ba, QIODevice::WriteOnly); ds << v; ba = ba.toBase64(); q.bindValue(":value", QString::fromLatin1(ba)); if (!q.exec()) { qWarning() << "Could not insert metadata" << q.lastError(); return false; } ++iter; } return true; } diff --git a/libs/resources/KisResourceIterator.cpp b/libs/resources/KisResourceIterator.cpp index 46e9015472..e8aa99734f 100644 --- a/libs/resources/KisResourceIterator.cpp +++ b/libs/resources/KisResourceIterator.cpp @@ -1,164 +1,164 @@ /* * Copyright (C) 2018 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 "KisResourceIterator.h" #include #include KisResourceItem::KisResourceItem(KisResourceModel *resourceModel, const QModelIndex &index) : m_resourceModel(resourceModel) , m_index(index) { } int KisResourceItem::id() { if (m_index.isValid()) { return m_index.data(Qt::UserRole + KisResourceModel::Id).toInt(); } return -1; } QString KisResourceItem::resourceType() { if (m_index.isValid()) { return m_index.data(Qt::UserRole + KisResourceModel::ResourceType).toString(); } return QString(); } QString KisResourceItem::name() { if (m_index.isValid()) { return m_index.data(Qt::UserRole + KisResourceModel::Name).toString(); } return QString(); } QString KisResourceItem::filename() { if (m_index.isValid()) { return m_index.data(Qt::UserRole + KisResourceModel::Filename).toString(); } return QString(); } QString KisResourceItem::tooltip() { if (m_index.isValid()) { return m_index.data(Qt::UserRole + KisResourceModel::Tooltip).toString(); } return QString(); } QImage KisResourceItem::thumbnail() { if (m_index.isValid()) { - return m_index.data(Qt::UserRole + KisResourceModel::Image).value(); + return m_index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); } return QImage(); } KoResourceSP KisResourceItem::resource() { if (m_index.isValid() && m_resourceModel) { return m_resourceModel->resourceForIndex(m_index); } return 0; } struct KisResourceIterator::Private { Private(KisResourceModel *_resourceModel) : resourceModel(_resourceModel) {} KisResourceModel *resourceModel {0}; int currentRow {0}; }; KisResourceIterator::KisResourceIterator(KisResourceModel *resourceModel) : d(new Private(resourceModel)) { } KisResourceIterator::~KisResourceIterator() { } bool KisResourceIterator::hasNext() const { return d->currentRow < d->resourceModel->rowCount() - 1; } bool KisResourceIterator::hasPrevious() const { return d->currentRow > 0 && d->resourceModel->rowCount() > 1 && d->currentRow < d->resourceModel->rowCount(); } const KisResourceItemSP KisResourceIterator::next() { if (d->currentRow < d->resourceModel->rowCount() - 1) { d->currentRow++; QModelIndex idx = d->resourceModel->index(d->currentRow, 0); return KisResourceItemSP(new KisResourceItem(d->resourceModel, idx)); } return KisResourceItemSP(new KisResourceItem(0, QModelIndex())); } const KisResourceItemSP KisResourceIterator::peekNext() const { if (d->currentRow < d->resourceModel->rowCount() - 2) { QModelIndex idx = d->resourceModel->index(d->currentRow + 1, 0); return KisResourceItemSP(new KisResourceItem(d->resourceModel, idx)); } return KisResourceItemSP(new KisResourceItem(0, QModelIndex())); } const KisResourceItemSP KisResourceIterator::peekPrevious() const { if (d->currentRow > 1 && d->resourceModel->rowCount() > 2) { QModelIndex idx = d->resourceModel->index(d->currentRow -1, 0); return KisResourceItemSP(new KisResourceItem(d->resourceModel, idx)); } return KisResourceItemSP(new KisResourceItem(0, QModelIndex())); } const KisResourceItemSP KisResourceIterator::previous() { if (d->currentRow > 1 && d->resourceModel->rowCount() > 2) { d->currentRow--; QModelIndex idx = d->resourceModel->index(d->currentRow, 0); return KisResourceItemSP(new KisResourceItem(d->resourceModel, idx)); } return KisResourceItemSP(new KisResourceItem(0, QModelIndex())); } void KisResourceIterator::toBack() { d->currentRow = 0; } void KisResourceIterator::toEnd() { d->currentRow = d->resourceModel->rowCount() -1; } diff --git a/libs/resources/KisResourceIterator.h b/libs/resources/KisResourceIterator.h index f5e051e5fe..4983abec6b 100644 --- a/libs/resources/KisResourceIterator.h +++ b/libs/resources/KisResourceIterator.h @@ -1,80 +1,83 @@ /* * Copyright (C) 2018 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 KISRESOURCEITERATOR_H #define KISRESOURCEITERATOR_H #include #include #include #include #include class KisResourceModel; #include /** * @brief The KisResourceItem class represents a resource, but until resource() is called, * the resource is not loaded; the rest of the information comes from the cache database. + * + * KisResourceItem is used in the KisStoragePlugin's resource iterators to populate the + * database. */ class KRITARESOURCES_EXPORT KisResourceItem { private: friend class KisResourceIterator; KisResourceItem(KisResourceModel *resourceModel, const QModelIndex &index); public: int id(); QString resourceType(); QString name(); QString filename(); QString tooltip(); QImage thumbnail(); KoResourceSP resource(); private: KisResourceModel *m_resourceModel; QModelIndex m_index; }; typedef QSharedPointer KisResourceItemSP; /** * @brief The KisResourceIterator class provides an iterator * for a KisResourceModel. */ class KRITARESOURCES_EXPORT KisResourceIterator { public: KisResourceIterator(KisResourceModel *resourceModel); ~KisResourceIterator(); bool hasNext() const; bool hasPrevious() const; const KisResourceItemSP next(); const KisResourceItemSP peekNext() const; const KisResourceItemSP peekPrevious() const; const KisResourceItemSP previous(); void toBack(); void toEnd(); private: struct Private; QScopedPointer d; }; #endif // KisResourceITERATOR_H diff --git a/libs/resources/KisResourceLoaderRegistry.h b/libs/resources/KisResourceLoaderRegistry.h index e4434615e0..fefaedd15a 100644 --- a/libs/resources/KisResourceLoaderRegistry.h +++ b/libs/resources/KisResourceLoaderRegistry.h @@ -1,81 +1,83 @@ /* * Copyright (C) 2018 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 KISRESOURCELOADERREGISTRY_H #define KISRESOURCELOADERREGISTRY_H #include #include #include #include #include "KisResourceLoader.h" #include /** * @brief The KisResourceLoaderRegistry class manages the loader plugins for resources. Every resource can be loaded * by a KisResourceLoader instance. A loader corresponds to a particular file type. Resources are organized in * folders that represent the main type of a certain resource (brushes) and subtypes, that identify a particular * resource format (gbr, gih, png, svg). + * + * KisResourceLoaderRegistry has full knowlege of all resource types that are defined for Krita. */ class KRITARESOURCES_EXPORT KisResourceLoaderRegistry : public QObject, public KoGenericRegistry { Q_OBJECT public: ~KisResourceLoaderRegistry() override; static KisResourceLoaderRegistry *instance(); /** * Adds the given loader and registers its type in the database, if it hasn't been registered yet. */ bool registerLoader(KisResourceLoaderBase* loader); /// @return the first loader for the given resource type and mimetype KisResourceLoaderBase *loader(const QString &resourceType, const QString &mimetype) const; /** * @return a list of filename extensions that can be present for the given resource type */ QStringList filters(const QString &resourceType) const; /** * @return a list of mimetypes that can be loaded for the given resourde type */ QStringList mimeTypes(const QString &resourceType) const; /** * @return the list of folders for which resource loaders have been registered */ QStringList resourceTypes() const; /** * @return a list of loader plugins that can handle the resources stored in the folder. A folder can contain multiple subtypes. */ QVector resourceTypeLoaders(const QString &resourceType) const; private: KisResourceLoaderRegistry(QObject *parent); KisResourceLoaderRegistry(const KisResourceLoaderRegistry&); KisResourceLoaderRegistry operator=(const KisResourceLoaderRegistry&); }; #endif // KISRESOURCELOADERREGISTRY_H diff --git a/libs/resources/KisResourceLocator.cpp b/libs/resources/KisResourceLocator.cpp index b106ae86c6..9b0e26c492 100644 --- a/libs/resources/KisResourceLocator.cpp +++ b/libs/resources/KisResourceLocator.cpp @@ -1,556 +1,615 @@ /* * Copyright (C) 2018 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 "KisResourceLocator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoResourcePaths.h" #include "KisResourceStorage.h" #include "KisResourceCacheDb.h" #include "KisResourceLoaderRegistry.h" #include "KisMemoryStorage.h" #include "KisResourceModelProvider.h" #include const QString KisResourceLocator::resourceLocationKey {"ResourceDirectory"}; class KisResourceLocator::Private { public: QString resourceLocation; QMap storages; QHash, KoResourceSP> resourceCache; QStringList errorMessages; }; KisResourceLocator::KisResourceLocator(QObject *parent) : QObject(parent) , d(new Private()) { } KisResourceLocator *KisResourceLocator::instance() { // Not a regular Q_GLOBAL_STATIC, because we want this deleted as // part of the app destructor. KisResourceLocator *locator = qApp->findChild(QString()); if (!locator) { locator = new KisResourceLocator(qApp); } return locator; } KisResourceLocator::~KisResourceLocator() { } KisResourceLocator::LocatorError KisResourceLocator::initialize(const QString &installationResourcesLocation) { InitalizationStatus initalizationStatus = InitalizationStatus::Unknown; KConfigGroup cfg(KSharedConfig::openConfig(), ""); d->resourceLocation = cfg.readEntry(resourceLocationKey, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); if (!d->resourceLocation.endsWith('/')) d->resourceLocation += '/'; QFileInfo fi(d->resourceLocation); if (!fi.exists()) { if (!QDir().mkpath(d->resourceLocation)) { d->errorMessages << i18n("1. Could not create the resource location at %1.", d->resourceLocation); return LocatorError::CannotCreateLocation; } initalizationStatus = InitalizationStatus::FirstRun; } if (!fi.isWritable()) { d->errorMessages << i18n("2. The resource location at %1 is not writable.", d->resourceLocation); return LocatorError::LocationReadOnly; } // Check whether we're updating from an older version if (initalizationStatus != InitalizationStatus::FirstRun) { QFile fi(d->resourceLocation + '/' + "KRITA_RESOURCE_VERSION"); if (!fi.exists()) { initalizationStatus = InitalizationStatus::FirstUpdate; } else { fi.open(QFile::ReadOnly); QVersionNumber resource_version = QVersionNumber::fromString(QString::fromUtf8(fi.readAll())); QVersionNumber krita_version = QVersionNumber::fromString(KritaVersionWrapper::versionString()); if (krita_version > resource_version) { initalizationStatus = InitalizationStatus::Updating; } else { initalizationStatus = InitalizationStatus::Initialized; } } } if (initalizationStatus != InitalizationStatus::Initialized) { KisResourceLocator::LocatorError res = firstTimeInstallation(initalizationStatus, installationResourcesLocation); if (res != LocatorError::Ok) { return res; } initalizationStatus = InitalizationStatus::Initialized; } else { if (!synchronizeDb()) { return LocatorError::CannotSynchronizeDb; } } return LocatorError::Ok; } QStringList KisResourceLocator::errorMessages() const { return d->errorMessages; } QString KisResourceLocator::resourceLocationBase() const { return d->resourceLocation; } bool KisResourceLocator::resourceCached(QString storageLocation, const QString &resourceType, const QString &filename) const { storageLocation = makeStorageLocationAbsolute(storageLocation); QPair key = QPair (storageLocation, resourceType + "/" + filename); return d->resourceCache.contains(key); } KoResourceSP KisResourceLocator::resource(QString storageLocation, const QString &resourceType, const QString &filename) { storageLocation = makeStorageLocationAbsolute(storageLocation); QPair key = QPair (storageLocation, resourceType + "/" + filename); KoResourceSP resource; if (d->resourceCache.contains(key)) { resource = d->resourceCache[key]; } else { KisResourceStorageSP storage = d->storages[storageLocation]; if (!storage) { qWarning() << "Could not find storage" << storageLocation; return 0; } resource = storage->resource(resourceType + "/" + filename); if (!resource) { qWarning() << "Could not find resource" << resourceType + "/" + filename; return 0; } d->resourceCache[key] = resource; } Q_ASSERT(resource); resource->setStorageLocation(storageLocation); Q_ASSERT(!resource->storageLocation().isEmpty()); return resource; } KoResourceSP KisResourceLocator::resourceForId(int resourceId) { ResourceStorage rs = getResourceStorage(resourceId); KoResourceSP r = resource(rs.storageLocation, rs.resourceType, rs.resourceFileName); if (r) { r->setResourceId(resourceId); } return r; } bool KisResourceLocator::removeResource(int resourceId, const QString &/*storageLocation*/) { // First remove the resource from the cache ResourceStorage rs = getResourceStorage(resourceId); QPair key = QPair (rs.storageLocation, rs.resourceType + "/" + rs.resourceFileName); d->resourceCache.remove(key); return KisResourceCacheDb::removeResource(resourceId); } bool KisResourceLocator::importResourceFromFile(const QString &resourceType, const QString &fileName, const QString &storageLocation) { KisResourceLoaderBase *loader = KisResourceLoaderRegistry::instance()->loader(resourceType, KisMimeDatabase::mimeTypeForFile(fileName)); QFile f(fileName); if (!f.open(QFile::ReadOnly)) { qWarning() << "Could not open" << fileName << "for loading"; return false; } KoResourceSP resource = loader->load(QFileInfo(fileName).fileName(), f, KisGlobalResourcesInterface::instance()); if (!resource) { qWarning() << "Could not import" << fileName << ": resource doesn't load."; return false; } KisResourceStorageSP storage = d->storages[makeStorageLocationAbsolute(storageLocation)]; Q_ASSERT(storage); if (!storage->addResource(resource)) { qWarning() << "Could not add resource" << resource->filename() << "to the folder storage"; return false; } return KisResourceCacheDb::addResource(folderStorage(), QFileInfo(resource->filename()).lastModified(), resource, resourceType); } bool KisResourceLocator::addResource(const QString &resourceType, const KoResourceSP resource, const QString &storageLocation) { if (!resource || !resource->valid()) return false; KisResourceStorageSP storage = d->storages[makeStorageLocationAbsolute(storageLocation)]; Q_ASSERT(storage); //If we have gotten this far and the resource still doesn't have a filename to save to, we should generate one. if (resource->filename().isEmpty()) { if (storageLocation == "memory") { resource->setFilename("memory/" + resourceType + "/" + resource->name()); } else { resource->setFilename(resource->name().split(" ").join("_") + resource->defaultFileExtension()); } } // Save the resource to the storage storage if (!storage->addResource(resource)) { qWarning() << "Could not add resource" << resource->filename() << "to the folder storage"; return false; } // And the database return KisResourceCacheDb::addResource(storage, QFileInfo(resource->filename()).lastModified(), resource, resourceType); } bool KisResourceLocator::updateResource(const QString &resourceType, const KoResourceSP resource) { QString storageLocation = makeStorageLocationAbsolute(resource->storageLocation()); + qDebug() << ">>>>>>>>>>>>>>>> storageLocation"<< storageLocation << "resource storage location" << resource->storageLocation(); + Q_ASSERT(d->storages.contains(storageLocation)); Q_ASSERT(resource->resourceId() > -1); KisResourceStorageSP storage = d->storages[storageLocation]; - - + resource->updateThumbnail(); int version = resource->version(); // This increments the version in the resource if (!storage->addResource(resource)) { qWarning() << "Failed to save the new version of " << resource->name() << "to storage" << storageLocation; return false; } // Memory storages don't store versioned resources if (storage->type() == KisResourceStorage::StorageType::Memory) { return true; } // It's the storages that keep track of the version Q_ASSERT(resource->version() == version + 1); // The version needs already to have been incremented if (!KisResourceCacheDb::addResourceVersion(resource->resourceId(), QDateTime::currentDateTime(), storage, resource)) { qWarning() << "Failed to add a new version of the resource to the database" << resource->name(); return false; } // Update the resource in the cache QPair key = QPair (storageLocation, resourceType + "/" + QFileInfo(resource->filename()).fileName()); d->resourceCache[key] = resource; return true; } QMap KisResourceLocator::metaDataForResource(int id) const { return KisResourceCacheDb::metaDataForId(id, "resources"); } bool KisResourceLocator::setMetaDataForResource(int id, QMap map) const { return KisResourceCacheDb::updateMetaDataForId(map, id, "resources"); } +QMap KisResourceLocator::metaDataForStorage(const QString &storageLocation) const +{ + QMap metadata; + if (!d->storages.contains(makeStorageLocationAbsolute(storageLocation))) { + qWarning() << storageLocation << "not in" << d->storages.keys(); + return metadata; + } + + KisResourceStorageSP st = d->storages[makeStorageLocationAbsolute(storageLocation)]; + + if (d->storages[makeStorageLocationAbsolute(storageLocation)].isNull()) { + return metadata; + } + + Q_FOREACH(const QString key, st->metaDataKeys()) { + metadata[key] = st->metaData(key); + } + return metadata; +} + +void KisResourceLocator::setMetaDataForStorage(const QString &storageLocation, QMap map) const +{ + Q_ASSERT(d->storages.contains(storageLocation)); + Q_FOREACH(const QString &key, map.keys()) { + d->storages[storageLocation]->setMetaData(key, map[key]); + } +} + +bool KisResourceLocator::storageContainsResourceByFile(const QString &storageLocation, const QString &resourceType, const QString &filename) const +{ + QSqlQuery q; + if (!q.prepare("SELECT *\n" + "FROM storages\n" + ", resources\n" + ", resource_types\n" + "WHERE resources.filename = :filename\n" + "AND resources.storage_id = storages.id\n" + "AND storages.location = :storage_location\n" + "AND resources.resource_type_id = resource_types.id\n" + "AND resource_types.name = :resource_type")) + { + qWarning() << "Could not prepare storageCOntainsResourceByFile query" << q.lastError(); + return false; + } + + q.bindValue(":filename", filename); + q.bindValue(":storage_location", storageLocation); + q.bindValue(":resource_type", resourceType); + + if (!q.exec()) { + qWarning() << "Could not execute storageCOntainsResourceByFile query" << q.lastError() << q.boundValues(); + return false; + } + + return q.first(); +} + void KisResourceLocator::purge() { d->resourceCache.clear(); } -bool KisResourceLocator::addStorage(const QString &document, KisResourceStorageSP storage) +bool KisResourceLocator::addStorage(const QString &storageLocation, KisResourceStorageSP storage) { - Q_ASSERT(!d->storages.contains(document)); + Q_ASSERT(!d->storages.contains(storageLocation)); - d->storages[document] = storage; + d->storages[storageLocation] = storage; if (!KisResourceCacheDb::addStorage(storage, false)) { d->errorMessages.append(i18n("Could not add %1 to the database", storage->location())); return false; } KisResourceModelProvider::resetAllModels(); return true; } bool KisResourceLocator::removeStorage(const QString &document) { // Cloned documents have a document storage, but that isn't in the locator. if (!d->storages.contains(document)) return true; purge(); - KisResourceStorageSP storage = d->storages.take(document); + KisResourceStorageSP storage = d->storages. take(document); if (!KisResourceCacheDb::deleteStorage(storage)) { d->errorMessages.append(i18n("Could not remove storage %1 from the database", storage->location())); return false; } KisResourceModelProvider::resetAllModels(); return true; } bool KisResourceLocator::hasStorage(const QString &document) { return d->storages.contains(document); } KisResourceLocator::LocatorError KisResourceLocator::firstTimeInstallation(InitalizationStatus initalizationStatus, const QString &installationResourcesLocation) { emit progressMessage(i18n("Krita is running for the first time. Intialization will take some time.")); Q_UNUSED(initalizationStatus); Q_FOREACH(const QString &folder, KisResourceLoaderRegistry::instance()->resourceTypes()) { QDir dir(d->resourceLocation + '/' + folder + '/'); if (!dir.exists()) { if (!QDir().mkpath(d->resourceLocation + '/' + folder + '/')) { d->errorMessages << i18n("3. Could not create the resource location at %1.", dir.path()); return LocatorError::CannotCreateLocation; } } } Q_FOREACH(const QString &folder, KisResourceLoaderRegistry::instance()->resourceTypes()) { QDir dir(installationResourcesLocation + '/' + folder + '/'); if (dir.exists()) { Q_FOREACH(const QString &entry, dir.entryList(QDir::Files | QDir::Readable)) { QFile f(dir.canonicalPath() + '/'+ entry); if (!QFileInfo(d->resourceLocation + '/' + folder + '/' + entry).exists()) { if (!f.copy(d->resourceLocation + '/' + folder + '/' + entry)) { d->errorMessages << i18n("Could not copy resource %1 to %2", f.fileName(), d->resourceLocation + '/' + folder + '/' + entry); } } } } } // And add bundles and adobe libraries QStringList filters = QStringList() << "*.bundle" << "*.abr" << "*.asl"; QDirIterator iter(installationResourcesLocation, filters, QDir::Files, QDirIterator::Subdirectories); while (iter.hasNext()) { iter.next(); emit progressMessage(i18n("Installing the resources from bundle %1.", iter.filePath())); QFile f(iter.filePath()); Q_ASSERT(f.exists()); if (!f.copy(d->resourceLocation + '/' + iter.fileName())) { d->errorMessages << i18n("Could not copy resource %1 to %2", f.fileName(), d->resourceLocation); } } QFile f(d->resourceLocation + '/' + "KRITA_RESOURCE_VERSION"); f.open(QFile::WriteOnly); f.write(KritaVersionWrapper::versionString().toUtf8()); f.close(); if (!initializeDb()) { return LocatorError::CannotInitializeDb; } return LocatorError::Ok; } bool KisResourceLocator::initializeDb() { emit progressMessage(i18n("Initalizing the resources.")); d->errorMessages.clear(); findStorages(); Q_FOREACH(KisResourceStorageSP storage, d->storages) { QTime t; t.start(); if (!KisResourceCacheDb::addStorage(storage, (storage->type() == KisResourceStorage::StorageType::Folder ? false : true))) { d->errorMessages.append(i18n("Could not add storage %1 to the cache database", storage->location())); } qDebug() << "Adding storage" << storage->location() << "to the database took" << t.elapsed() << "ms"; } return (d->errorMessages.isEmpty()); } void KisResourceLocator::findStorages() { d->storages.clear(); // Add the folder KisResourceStorageSP storage = QSharedPointer::create(d->resourceLocation); Q_ASSERT(storage->location() == d->resourceLocation); d->storages[d->resourceLocation] = storage; // Add the memory storage d->storages["memory"] = QSharedPointer::create("memory"); + d->storages["memory"]->setMetaData(KisResourceStorage::s_meta_name, i18n("Temporary Resources")); // And add bundles and adobe libraries QStringList filters = QStringList() << "*.bundle" << "*.abr" << "*.asl"; QDirIterator iter(d->resourceLocation, filters, QDir::Files, QDirIterator::Subdirectories); while (iter.hasNext()) { iter.next(); KisResourceStorageSP storage = QSharedPointer::create(iter.filePath()); d->storages[storage->location()] = storage; } } QList KisResourceLocator::storages() const { return d->storages.values(); } KisResourceStorageSP KisResourceLocator::storageByLocation(const QString &location) const { if (!d->storages.contains(location)) { - qWarning() << "No" << location << "storage defined"; + qWarning() << "No" << location << "storage defined:" << d->storages.keys(); return 0; } KisResourceStorageSP storage = d->storages[location]; if (!storage || !storage->valid()) { qWarning() << "Could not retrieve the" << location << "storage object or the object is not valid"; return 0; } return storage; } KisResourceStorageSP KisResourceLocator::folderStorage() const { return storageByLocation(d->resourceLocation); } KisResourceStorageSP KisResourceLocator::memoryStorage() const { return storageByLocation("memory"); } KisResourceLocator::ResourceStorage KisResourceLocator::getResourceStorage(int resourceId) const { ResourceStorage rs; QSqlQuery q; bool r = q.prepare("SELECT storages.location\n" ", resource_types.name as resource_type\n" ", resources.filename\n" "FROM resources\n" ", storages\n" ", resource_types\n" "WHERE resources.id = :resource_id\n" "AND resources.storage_id = storages.id\n" "AND resource_types.id = resources.resource_type_id"); if (!r) { qWarning() << "KisResourceLocator::removeResource: could not prepare query." << q.lastError(); return rs; } q.bindValue(":resource_id", resourceId); r = q.exec(); if (!r) { qWarning() << "KisResourceLocator::removeResource: could not execute query." << q.lastError(); return rs; } q.first(); QString storageLocation = q.value("location").toString(); QString resourceType= q.value("resource_type").toString(); QString resourceFilename = q.value("filename").toString(); rs.storageLocation = makeStorageLocationAbsolute(storageLocation); rs.resourceType = resourceType; rs.resourceFileName = resourceFilename; return rs; } QString KisResourceLocator::makeStorageLocationAbsolute(QString storageLocation) const { if (storageLocation.isEmpty()) { - storageLocation = resourceLocationBase(); + return resourceLocationBase(); } - // XXX: This breaks with document storages! - - if (!storageLocation.startsWith('/') && storageLocation != "memory") { + if (!storageLocation.startsWith('/') && (storageLocation.endsWith("bundle") + || storageLocation.endsWith("asl") + || storageLocation.endsWith("abr"))) { if (resourceLocationBase().endsWith('/')) { storageLocation = resourceLocationBase() + storageLocation; } else { storageLocation = resourceLocationBase() + '/' + storageLocation; } } return storageLocation; } bool KisResourceLocator::synchronizeDb() { d->errorMessages.clear(); findStorages(); Q_FOREACH(const KisResourceStorageSP storage, d->storages) { if (!KisResourceCacheDb::synchronizeStorage(storage)) { d->errorMessages.append(i18n("Could not synchronize %1 with the database", storage->location())); } } return d->errorMessages.isEmpty(); } QString KisResourceLocator::makeStorageLocationRelative(QString location) const { return location.remove(resourceLocationBase()); } diff --git a/libs/resources/KisResourceLocator.h b/libs/resources/KisResourceLocator.h index 6f3b43d4bd..e860948081 100644 --- a/libs/resources/KisResourceLocator.h +++ b/libs/resources/KisResourceLocator.h @@ -1,241 +1,271 @@ /* * Copyright (C) 2018 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 KISRESOURCELOCATOR_H #define KISRESOURCELOCATOR_H #include #include #include #include #include "kritaresources_export.h" #include /** * The KisResourceLocator class locates all resource storages (folders, * bundles, various adobe resource libraries) in the resource location. * * The resource location is always a writable folder. * * There is one resource locator which is owned by the QApplication * object. * * The resource location is configurable, but there is only one location * where Krita will look for resources. */ class KRITARESOURCES_EXPORT KisResourceLocator : public QObject { Q_OBJECT public: // The configuration key that holds the resource location // for this installation of Krita. The location is // QStandardPaths::AppDataLocation by default, but that // can be changed. static const QString resourceLocationKey; static KisResourceLocator *instance(); ~KisResourceLocator(); enum class LocatorError { Ok, LocationReadOnly, CannotCreateLocation, CannotInitializeDb, CannotSynchronizeDb }; /** * @brief initialize Setup the resource locator for use. * * @param installationResourcesLocation the place where the resources * that come packaged with Krita reside. */ LocatorError initialize(const QString &installationResourcesLocation); /** * @brief errorMessages * @return */ QStringList errorMessages() const; /** * @brief resourceLocationBase is the place where all resource storages (folder, * bundles etc. are located. This is a writable place. * @return the base location for all storages. */ QString resourceLocationBase() const; /** * @brief purge purges the local resource cache */ void purge(); /** * @brief addStorage Adds a new resource storage to the database. The storage is * will be marked as not pre-installed. * @param storageLocation a unique name for the given storage * @param storage a storage object * @return true if the storage has been added succesfully */ bool addStorage(const QString &storageLocation, KisResourceStorageSP storage); /** * @brief removeStorage removes the temporary storage from the database * @param document the unique name of the document * @return true is succesful. */ bool removeStorage(const QString &storageLocation); /** * @brief hasStorage can be used to check whether the given storage already exists * @param storageLocation the name of the storage * @return true if the storage is known */ bool hasStorage(const QString &storageLocation); Q_SIGNALS: void progressMessage(const QString&); private: friend class KisResourceModel; friend class KisTagModel; friend class KisStorageModel; friend class TestResourceLocator; friend class TestResourceModel; friend class Resource; friend class KisResourceCacheDb; + friend class KisStorageFilterProxyModel; /// @return true if the resource is present in the cache, false if it hasn't been loaded bool resourceCached(QString storageLocation, const QString &resourceType, const QString &filename) const; /** * @brief resource finds a physical resource in one of the storages * @param storageLocation the storage containing the resource. If empty, * this is the folder storage. * * Note that the resource does not have the version or id field set, so this cannot be used directly, * but only through KisResourceModel. * * @param resourceType the type of the resource * @param filename the filename of the resource including extension, but withou * any paths * @return A resource if found, or 0 */ KoResourceSP resource(QString storageLocation, const QString &resourceType, const QString &filename); /** * @brief resourceForId returns the resource with the given id, or 0 if no such resource exists. * The resource object will have its id set but not its version. * @param resourceId the id */ KoResourceSP resourceForId(int resourceId); /** * @brief removeResource * @param resourceId * @param optional: the storage that contains the given resource * @return */ bool removeResource(int resourceId, const QString &storageLocation = QString()); /** * @brief importResourceFromFile * @param resourceType * @param fileName * @param storageLocation: optional, the storage where the resource will be stored. Empty means in the default Folder storage. * @return */ bool importResourceFromFile(const QString &resourceType, const QString &fileName, const QString &storageLocation = QString()); /** * @brief addResource adds the given resource to the database and potentially a storage * @param resourceType the type of the resource * @param resource the actual resource object * @param storageLocation the storage where the resource will be saved. By default this is the the default folder storage. * @return true if succesfull */ bool addResource(const QString &resourceType, const KoResourceSP resource, const QString &storageLocation = QString()); /** * @brief updateResource * @param resourceType * @param resource * @return */ bool updateResource(const QString &resourceType, const KoResourceSP resource); /** * @brief metaDataForResource * @param id * @return */ QMap metaDataForResource(int id) const; + /** + * @brief setMetaDataForResource + * @param id + * @param map + * @return + */ bool setMetaDataForResource(int id, QMap map) const; + /** + * @brief metaDataForStorage + * @param storage + * @return + */ + QMap metaDataForStorage(const QString &storageLocation) const; + + /** + * @brief setMetaDataForStorage + * @param storage + * @param map + */ + void setMetaDataForStorage(const QString &storageLocation, QMap map) const; + + /** + * @brief storageContainsResourceByFile + * @param storageLocation + * @param filename + * @return + */ + bool storageContainsResourceByFile(const QString &storageLocation, const QString &resourceType, const QString &filename) const; + + KisResourceLocator(QObject *parent); KisResourceLocator(const KisResourceLocator&); KisResourceLocator operator=(const KisResourceLocator&); enum class InitalizationStatus { Unknown, // We don't know whether Krita has run on this system for this resource location yet Initialized, // Everything is ready to start synchronizing the database FirstRun, // Krita hasn't run for this resource location yet FirstUpdate, // Krita was installed, but it's a version from before the resource locator existed, only user-defined resources are present Updating // Krita is updating from an older version with resource locator }; LocatorError firstTimeInstallation(InitalizationStatus initalizationStatus, const QString &installationResourcesLocation); // First time installation bool initializeDb(); // Synchronize on restarting Krita to see whether the user has added any storages or resources to the resources location bool synchronizeDb(); void findStorages(); QList storages() const; KisResourceStorageSP storageByLocation(const QString &location) const; KisResourceStorageSP folderStorage() const; KisResourceStorageSP memoryStorage() const; struct ResourceStorage { QString storageLocation; QString resourceType; QString resourceFileName; - }; + }; ResourceStorage getResourceStorage(int resourceId) const; QString makeStorageLocationAbsolute(QString storageLocation) const; QString makeStorageLocationRelative(QString location) const; class Private; QScopedPointer d; }; #endif // KISRESOURCELOCATOR_H diff --git a/libs/resources/KisResourceModel.cpp b/libs/resources/KisResourceModel.cpp index 5ec61cf376..7cb657bdcf 100644 --- a/libs/resources/KisResourceModel.cpp +++ b/libs/resources/KisResourceModel.cpp @@ -1,566 +1,571 @@ /* * Copyright (C) 2018 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 "KisResourceModel.h" #include #include #include #include #include #include #include #include struct KisResourceModel::Private { QSqlQuery resourcesQuery; QSqlQuery tagQuery; QString resourceType; int columnCount {9}; int cachedRowCount {-1}; }; //static int s_i = 0; KisResourceModel::KisResourceModel(const QString &resourceType, QObject *parent) : QAbstractTableModel(parent) , d(new Private) { //qDebug() << "ResourceModel" << s_i << resourceType; s_i++; d->resourceType = resourceType; bool r = d->resourcesQuery.prepare("SELECT resources.id\n" ", resources.storage_id\n" ", resources.name\n" ", resources.filename\n" ", resources.tooltip\n" ", resources.thumbnail\n" ", resources.status\n" ", storages.location\n" ", resources.version\n" ", resource_types.name as resource_type\n" "FROM resources\n" ", resource_types\n" ", storages\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resources.storage_id = storages.id\n" "AND resource_types.name = :resource_type\n" "AND resources.status = 1\n" "AND storages.active = 1"); if (!r) { qWarning() << "Could not prepare KisResourceModel query" << d->resourcesQuery.lastError(); } d->resourcesQuery.bindValue(":resource_type", d->resourceType); resetQuery(); r = d->tagQuery.prepare("SELECT tags.id\n" ", tags.url\n" ", tags.name\n" ", tags.comment\n" "FROM tags\n" ", resource_tags\n" "WHERE tags.active > 0\n" // make sure the tag is active "AND tags.id = resource_tags.tag_id\n" // join tags + resource_tags by tag_id "AND resource_tags.resource_id = :resource_id\n"); // make sure we're looking for tags for a specific resource if (!r) { qWarning() << "Could not prepare TagsForResource query" << d->tagQuery.lastError(); } } KisResourceModel::~KisResourceModel() { delete d; } int KisResourceModel::columnCount(const QModelIndex &/*parent*/) const { return d->columnCount; } QVariant KisResourceModel::data(const QModelIndex &index, int role) const { QVariant v; if (!index.isValid()) return v; if (index.row() > rowCount()) return v; if (index.column() > d->columnCount) return v; bool pos = const_cast(this)->d->resourcesQuery.seek(index.row()); if (pos) { switch(role) { case Qt::DisplayRole: { switch(index.column()) { case Id: return d->resourcesQuery.value("id"); case StorageId: return d->resourcesQuery.value("storage_id"); case Name: return d->resourcesQuery.value("name"); case Filename: return d->resourcesQuery.value("filename"); case Tooltip: return d->resourcesQuery.value("tooltip"); - case Image: + case Thumbnail: { QByteArray ba = d->resourcesQuery.value("thumbnail").toByteArray(); QBuffer buf(&ba); buf.open(QBuffer::ReadOnly); QImage img; img.load(&buf, "PNG"); return QVariant::fromValue(img); } case Status: return d->resourcesQuery.value("status"); case Location: return d->resourcesQuery.value("location"); case ResourceType: return d->resourcesQuery.value("resource_type"); default: ; }; Q_FALLTHROUGH(); } case Qt::DecorationRole: { - if (index.column() == Image) { + if (index.column() == Thumbnail) { QByteArray ba = d->resourcesQuery.value("thumbnail").toByteArray(); QBuffer buf(&ba); buf.open(QBuffer::ReadOnly); QImage img; img.load(&buf, "PNG"); return QVariant::fromValue(img); } return QVariant(); } case Qt::ToolTipRole: Q_FALLTHROUGH(); case Qt::StatusTipRole: Q_FALLTHROUGH(); case Qt::WhatsThisRole: return d->resourcesQuery.value("tooltip"); case Qt::UserRole + Id: return d->resourcesQuery.value("id"); case Qt::UserRole + StorageId: return d->resourcesQuery.value("storage_id"); case Qt::UserRole + Name: return d->resourcesQuery.value("name"); case Qt::UserRole + Filename: return d->resourcesQuery.value("filename"); case Qt::UserRole + Tooltip: return d->resourcesQuery.value("tooltip"); - case Qt::UserRole + Image: + case Qt::UserRole + Thumbnail: { QByteArray ba = d->resourcesQuery.value("thumbnail").toByteArray(); QBuffer buf(&ba); buf.open(QBuffer::ReadOnly); QImage img; img.load(&buf, "PNG"); return QVariant::fromValue(img); } case Qt::UserRole + Status: return d->resourcesQuery.value("status"); case Qt::UserRole + Location: return d->resourcesQuery.value("location"); case Qt::UserRole + ResourceType: return d->resourcesQuery.value("resource_type"); case Qt::UserRole + Tags: { QVector tags = tagsForResource(d->resourcesQuery.value("id").toInt()); QStringList tagNames; Q_FOREACH(const KisTagSP tag, tags) { tagNames << tag->name(); } return tagNames; } case Qt::UserRole + Dirty: { QString storageLocation = d->resourcesQuery.value("location").toString(); QString filename = d->resourcesQuery.value("filename").toString(); // An uncached resource has not been loaded, so it cannot be dirty if (!KisResourceLocator::instance()->resourceCached(storageLocation, d->resourceType, filename)) { return false; } else { // Now we have to check the resource, but that's cheap since it's been loaded in any case KoResourceSP resource = resourceForIndex(index); return resource->isDirty(); } } case Qt::UserRole + MetaData: { QMap r = KisResourceLocator::instance()->metaDataForResource(d->resourcesQuery.value("id").toInt()); return r; } case Qt::UserRole + KoResourceRole: { KoResourceSP tag = resourceForIndex(index); QVariant response; response.setValue(tag); return response; } default: ; } } return v; } QVariant KisResourceModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant v = QVariant(); if (role != Qt::DisplayRole) { return v; } if (orientation == Qt::Horizontal) { switch(section) { case Id: v = i18n("Id"); break; case StorageId: v = i18n("Storage ID"); break; case Name: v = i18n("Name"); break; case Filename: v = i18n("File Name"); break; case Tooltip: v = i18n("Tooltip"); break; - case Image: + case Thumbnail: v = i18n("Image"); break; case Status: v = i18n("Status"); break; case Location: v = i18n("Location"); break; case ResourceType: v = i18n("Resource Type"); break; default: v = QString::number(section); } return v; } return QAbstractItemModel::headerData(section, orientation, role); } //static int s_i2 {0}; KoResourceSP KisResourceModel::resourceForIndex(QModelIndex index) const { KoResourceSP resource = 0; if (!index.isValid()) return resource; if (index.row() > rowCount()) return resource; if (index.column() > d->columnCount) return resource; //qDebug() << "KisResourceModel::resourceForIndex" << s_i2 << d->resourceType; s_i2++; bool pos = const_cast(this)->d->resourcesQuery.seek(index.row()); if (pos) { - QString storageLocation = d->resourcesQuery.value("location").toString(); - QString filename = d->resourcesQuery.value("filename").toString(); - resource = KisResourceLocator::instance()->resource(storageLocation, d->resourceType, filename); - resource->setResourceId(d->resourcesQuery.value("id").toInt()); - resource->setVersion(d->resourcesQuery.value("version").toInt()); - resource->setFilename(filename); - resource->setStorageLocation(storageLocation); - QString name = d->resourcesQuery.value("name").toString(); - if (!name.isNull()) { - resource->setName(name); - } + int id = d->resourcesQuery.value("id").toInt(); + resource = resourceForId(id); } return resource; } KoResourceSP KisResourceModel::resourceForId(int id) const { return KisResourceLocator::instance()->resourceForId(id); } KoResourceSP KisResourceModel::resourceForFilename(QString filename) const { KoResourceSP resource = 0; QSqlQuery q; bool r = q.prepare("SELECT resources.id AS id\n" "FROM resources\n" ", resource_types\n" ", storages\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resources.storage_id = storages.id\n" "AND resources.filename = :resource_filename\n" "AND resource_types.name = :resource_type\n" "AND resources.status = 1\n" "AND storages.active = 1"); if (!r) { qWarning() << "Could not prepare KisResourceModel query for resource name" << q.lastError(); } q.bindValue(":resource_filename", filename); q.bindValue(":resource_type", d->resourceType); r = q.exec(); if (!r) { qWarning() << "Could not select" << d->resourceType << "resources by filename" << q.lastError() << q.boundValues(); } if (q.first()) { int id = q.value("id").toInt(); resource = KisResourceLocator::instance()->resourceForId(id); } return resource; } KoResourceSP KisResourceModel::resourceForName(QString name) const { KoResourceSP resource = 0; QSqlQuery q; bool r = q.prepare("SELECT resources.id AS id\n" "FROM resources\n" ", resource_types\n" ", storages\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resources.storage_id = storages.id\n" "AND resources.name = :resource_name\n" "AND resource_types.name = :resource_type\n" "AND resources.status = 1\n" "AND storages.active = 1"); if (!r) { qWarning() << "Could not prepare KisResourceModel query for resource name" << q.lastError(); } q.bindValue(":resource_type", d->resourceType); q.bindValue(":resource_name", name); r = q.exec(); if (!r) { qWarning() << "Could not select" << d->resourceType << "resources by name" << q.lastError() << q.boundValues(); } if (q.first()) { int id = q.value("id").toInt(); resource = KisResourceLocator::instance()->resourceForId(id); } return resource; } KoResourceSP KisResourceModel::resourceForMD5(const QByteArray md5sum) const { KoResourceSP resource = 0; QSqlQuery q; bool r = q.prepare("SELECT resource_id AS id\n" "FROM versioned_resources\n" "WHERE md5sum = :md5sum"); if (!r) { qWarning() << "Could not prepare KisResourceModel query for resource md5" << q.lastError(); } q.bindValue(":md5sum", md5sum.toHex()); r = q.exec(); if (!r) { qWarning() << "Could not select" << d->resourceType << "resources by md5" << q.lastError() << q.boundValues(); } if (q.first()) { int id = q.value("id").toInt(); resource = KisResourceLocator::instance()->resourceForId(id); } return resource; } //static int s_i3 {0}; QModelIndex KisResourceModel::indexFromResource(KoResourceSP resource) const { if (!resource || !resource->valid()) return QModelIndex(); //qDebug() << "KisResourceModel::indexFromResource" << s_i3 << d->resourceType; s_i3++; // For now a linear seek to find the first resource with the right id d->resourcesQuery.first(); do { if (d->resourcesQuery.value("id").toInt() == resource->resourceId()) { return createIndex(d->resourcesQuery.at(), 0); } } while (d->resourcesQuery.next()); return QModelIndex(); } //static int s_i4 {0}; bool KisResourceModel::removeResource(const QModelIndex &index) { if (index.row() > rowCount()) return false; if (index.column() > d->columnCount) return false; //qDebug() << "KisResourceModel::removeResource" << s_i4 << d->resourceType; s_i4++; bool pos = d->resourcesQuery.seek(index.row()); if (!pos) return false; int resourceId = d->resourcesQuery.value("id").toInt(); if (!KisResourceLocator::instance()->removeResource(resourceId)) { qWarning() << "Failed to remove resource" << resourceId; return false; } return resetQuery(); } //static int s_i5 {0}; bool KisResourceModel::removeResource(KoResourceSP resource) { if (!resource || !resource->valid()) return false; //qDebug() << "KisResourceModel::remvoeResource 2" << s_i5 << d->resourceType; s_i5++; if (!KisResourceLocator::instance()->removeResource(resource->resourceId())) { qWarning() << "Failed to remove resource" << resource->resourceId(); return false; } return resetQuery(); } //static int s_i6 {0}; bool KisResourceModel::importResourceFile(const QString &filename) { //qDebug() << "KisResourceModel::importResource" << s_i6 << d->resourceType; s_i6++; if (!KisResourceLocator::instance()->importResourceFromFile(d->resourceType, filename)) { qWarning() << "Failed to import resource" << filename; return false; } return resetQuery(); } //static int s_i7 {0}; bool KisResourceModel::addResource(KoResourceSP resource, const QString &storageId) { if (!resource || !resource->valid()) { qWarning() << "Cannot add resource. Resource is null or not valid"; return false; } //qDebug() << "KisResourceModel::addResource" << s_i7 << d->resourceType; s_i7++; if (!KisResourceLocator::instance()->addResource(d->resourceType, resource, storageId)) { qWarning() << "Failed to add resource" << resource->name(); return false; } return resetQuery(); } //static int s_i8 {0}; bool KisResourceModel::updateResource(KoResourceSP resource) { if (!resource || !resource->valid()) { qWarning() << "Cannot update resource. Resource is null or not valid"; return false; } //qDebug() << "KisResourceModel::updateResource" << s_i8 << d->resourceType; s_i8++; if (!KisResourceLocator::instance()->updateResource(d->resourceType, resource)) { - qWarning() << "Failed to update resource"; + qWarning() << "Failed to update resource" << resource; + return false; + } + return resetQuery(); +} + +bool KisResourceModel::renameResource(KoResourceSP resource, const QString &name) +{ + if (!resource || !resource->valid() || name.isEmpty()) { + qWarning() << "Cannot rename resources. Resource is NULL or not valid or name is empty"; + return false; + } + resource->setName(name); + if (!KisResourceLocator::instance()->updateResource(d->resourceType, resource)) { + qWarning() << "Failed to rename resource" << resource << name; return false; } return resetQuery(); } //static int s_i9 {0}; bool KisResourceModel::setResourceMetaData(KoResourceSP resource, QMap metadata) { //qDebug() << "KisResourceModel::setResourceMetaData" << s_i9 << d->resourceType; s_i9++; Q_ASSERT(resource->resourceId() > -1); return KisResourceLocator::instance()->setMetaDataForResource(resource->resourceId(), metadata); } bool KisResourceModel::resetQuery() { QTime t; t.start(); emit beforeResourcesLayoutReset(QModelIndex()); beginResetModel(); bool r = d->resourcesQuery.exec(); if (!r) { qWarning() << "Could not select" << d->resourceType << "resources" << d->resourcesQuery.lastError() << d->resourcesQuery.boundValues(); } d->cachedRowCount = -1; endResetModel(); emit afterResourcesLayoutReset(); qDebug() << "KisResourceModel::resetQuery for" << d->resourceType << "took" << t.elapsed() << "ms"; return r; } QVector KisResourceModel::tagsForResource(int resourceId) const { return KisTagModelProvider::tagModel(d->resourceType)->tagsForResource(resourceId); } int KisResourceModel::rowCount(const QModelIndex &) const { if (d->cachedRowCount < 0) { QSqlQuery q; q.prepare("SELECT count(*)\n" "FROM resources\n" ", resource_types\n" ", storages\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n" "AND resources.storage_id = storages.id\n" "AND resources.status = 1\n" "AND storages.active = 1"); q.bindValue(":resource_type", d->resourceType); q.exec(); q.first(); const_cast(this)->d->cachedRowCount = q.value(0).toInt(); } return d->cachedRowCount; } diff --git a/libs/resources/KisResourceModel.h b/libs/resources/KisResourceModel.h index 20ce1a411e..7e15f38087 100644 --- a/libs/resources/KisResourceModel.h +++ b/libs/resources/KisResourceModel.h @@ -1,200 +1,216 @@ /* * Copyright (C) 2018 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 KISRESOURCEMODEL_H #define KISRESOURCEMODEL_H #include #include #include #include +/** + * KisAbstractResourceModel defines the interface for accessing resources + * that is used in KisResourceModel and the various filter/proxy models + */ class KRITARESOURCES_EXPORT KisAbstractResourceModel { public: virtual ~KisAbstractResourceModel(){} /** * @brief resourceForIndex * @param index * @return */ virtual KoResourceSP resourceForIndex(QModelIndex index = QModelIndex()) const = 0; /** * @brief indexFromResource * @param resource * @return */ virtual QModelIndex indexFromResource(KoResourceSP resource) const = 0; /** * @brief removeResource * @param index * @return */ virtual bool removeResource(const QModelIndex &index) = 0; /** * @brief importResourceFile * @param filename * @return */ virtual bool importResourceFile(const QString &filename) = 0; /** * @brief addResource adds the given resource to the database and storage * @param resource the resource itself * @param storageId the id of the storage (could be "memory" for temporary * resources, the document's storage id for document storages or empty to save * to the default resources folder * @return true if adding the resoruce succeded. */ virtual bool addResource(KoResourceSP resource, const QString &storageId = QString()) = 0; /** * @brief updateResource * @param resource * @return */ virtual bool updateResource(KoResourceSP resource) = 0; + /** + * @brief renameResource name the given resource. The resource will have its + * name field reset, will be saved to the storage and there will be a new + * version created in the database. + * @param resource The resource to rename + * @param name The new name + * @return true if the operation succeeded. + */ + virtual bool renameResource(KoResourceSP resource, const QString &name) = 0; + /** * @brief removeResource * @param resource * @return */ virtual bool removeResource(KoResourceSP resource) = 0; /** * @brief setResourceMetaData * @param metadata * @return */ virtual bool setResourceMetaData(KoResourceSP resource, QMap metadata) = 0; + + }; /** * @brief The KisResourceModel class provides access to the cache database * for a particular resource type. Instances should be retrieved using * KisResourceModelProvider. */ class KRITARESOURCES_EXPORT KisResourceModel : public QAbstractTableModel, public KisAbstractResourceModel { Q_OBJECT public: /** * @brief The Columns enum indexes the columns in the model. To get * the thumbnail for a particular resource, create the index with * QModelIndex(row, Thumbnail). */ enum Columns { Id = 0, StorageId, Name, Filename, Tooltip, - Image, + Thumbnail, Status, Location, ResourceType, Tags, /// A larger thumbnail for displaying in a tooltip. 200x200 or so. LargeThumbnail, /// A dirty resource is one that has been modified locally but not saved Dirty, /// MetaData is a map of key, value pairs that is associated with this resource MetaData, KoResourceRole }; private: friend class KisResourceModelProvider; friend class TestResourceModel; KisResourceModel(const QString &resourceType, QObject *parent = 0); public: ~KisResourceModel() override; // QAbstractItemModel API int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; // Resources API /** * @brief resourceForIndex returns a properly versioned and id's resource object */ KoResourceSP resourceForIndex(QModelIndex index = QModelIndex()) const override; KoResourceSP resourceForId(int id) const; /** * resourceForFilename returns the first resource with the given filename that * is active and is in an active store. Note that the filename does not include * a path to the storage, and if there are resources with the same filename * in several active storages, only one resource is returned. * * @return a resource if one is found, or 0 if none are found */ KoResourceSP resourceForFilename(QString fileName) const; /** * resourceForName returns the first resource with the given name that * is active and is in an active store. Note that if there are resources * with the same name in several active storages, only one resource * is returned. * * @return a resource if one is found, or 0 if none are found */ KoResourceSP resourceForName(QString name) const; KoResourceSP resourceForMD5(const QByteArray md5sum) const; QModelIndex indexFromResource(KoResourceSP resource) const override; bool removeResource(const QModelIndex &index) override; bool removeResource(KoResourceSP resource) override; bool importResourceFile(const QString &filename) override; bool addResource(KoResourceSP resource, const QString &storageId = QString()) override; bool updateResource(KoResourceSP resource) override; + bool renameResource(KoResourceSP resource, const QString &name) override; bool setResourceMetaData(KoResourceSP resource, QMap metadata) override; QVector tagsForResource(int resourceId) const; - Q_SIGNALS: // XXX: emit these signals void beforeResourcesLayoutReset(QModelIndex activateAfterReformat); void afterResourcesLayoutReset(); private: bool resetQuery(); struct Private; Private *const d; }; #endif // KISRESOURCEMODEL_H diff --git a/libs/resources/KisResourceModelProvider.h b/libs/resources/KisResourceModelProvider.h index 396409c214..12d99e5083 100644 --- a/libs/resources/KisResourceModelProvider.h +++ b/libs/resources/KisResourceModelProvider.h @@ -1,45 +1,50 @@ /* * Copyright (c) 2018 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 KISRESOURCEMODELPROVIDER_H #define KISRESOURCEMODELPROVIDER_H #include #include "kritaresources_export.h" class KisResourceModel; +/** + * KisResourceModelProvider should be used to retrieve resource models. + * For every resource type, there is only one instance of the resource model, + * so all views on these models show the same state. + */ class KRITARESOURCES_EXPORT KisResourceModelProvider { public: KisResourceModelProvider(); ~KisResourceModelProvider(); static KisResourceModel *resourceModel(const QString &resourceType); static void resetAllModels(); static void resetModel(const QString& resourceType); private: struct Private; Private *const d; Q_DISABLE_COPY(KisResourceModelProvider) }; #endif // KISRESOURCEMODELPROVIDER_H diff --git a/libs/resources/KisResourceSearchBoxFilter.cpp b/libs/resources/KisResourceSearchBoxFilter.cpp index 9a733c5138..4a46a9782f 100644 --- a/libs/resources/KisResourceSearchBoxFilter.cpp +++ b/libs/resources/KisResourceSearchBoxFilter.cpp @@ -1,153 +1,158 @@ /* * Copyright (c) 2019 Agata Cacko * * 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 "KisResourceSearchBoxFilter.h" #include #include #include #include class Q_DECL_HIDDEN KisResourceSearchBoxFilter::Private { public: Private() : searchTokenizer("\\s*,+\\s*") {} QRegularExpression searchTokenizer; QChar tagBegin {'['}; QChar tagEnd {']'}; QChar exactMatchBeginEnd {'"'}; QSet tagNamesIncluded; QSet tagNamesExcluded; QList resourceNamesPartsIncluded; QList resourceNamesPartsExcluded; QSet resourceExactMatchesIncluded; QSet resourceExactMatchesExcluded; QString filter; }; KisResourceSearchBoxFilter::KisResourceSearchBoxFilter() : d(new Private()) { } QString cutOutDelimeters(QString text) { QString response(text); response = response.remove(0, 1); response = response.left(response.length() - 1); return response; } void KisResourceSearchBoxFilter::setFilter(const QString& filter) { d->filter = QString(filter); initializeFilterData(); } bool KisResourceSearchBoxFilter::matchesResource(const QString &resourceName) { // exact matches if (d->resourceExactMatchesIncluded.count() > 0 && !d->resourceExactMatchesIncluded.contains(resourceName)) { return false; } if (d->resourceExactMatchesExcluded.contains(resourceName)) { return false; } // partial name matches if (d->resourceNamesPartsIncluded.count() > 0) { Q_FOREACH(const QString& partialName, d->resourceNamesPartsIncluded) { if (!resourceName.contains(partialName)) { return false; } } } Q_FOREACH(const QString& partialName, d->resourceNamesPartsExcluded) { if (resourceName.contains(partialName)) { return false; } } // tags matches return true; } +bool KisResourceSearchBoxFilter::isEmpty() +{ + return d->filter.isEmpty(); +} + void KisResourceSearchBoxFilter::clearFilterData() { d->tagNamesIncluded.clear(); d->tagNamesExcluded.clear(); d->resourceNamesPartsIncluded.clear(); d->resourceNamesPartsExcluded.clear(); d->resourceExactMatchesIncluded.clear(); d->resourceExactMatchesExcluded.clear(); } void KisResourceSearchBoxFilter::initializeFilterData() { clearFilterData(); QString tempFilter(d->filter); QStringList parts = tempFilter.split(d->searchTokenizer, QString::SkipEmptyParts); Q_FOREACH(const QString& partFor, parts) { QString part(partFor); bool included = true; if (part.startsWith('!')) { part.remove(0, 1); included = false; } if (part.startsWith(d->tagBegin) && part.endsWith(d->tagEnd)) { QString tagMatchCaptured = cutOutDelimeters(part); if (included) { d->tagNamesIncluded.insert(tagMatchCaptured); } else { d->tagNamesExcluded.insert(tagMatchCaptured); } } else if (part.startsWith(d->exactMatchBeginEnd) && part.endsWith(d->exactMatchBeginEnd)) { QString exactMatchCaptured = cutOutDelimeters(part); if (included) { d->resourceExactMatchesIncluded.insert(exactMatchCaptured); } else { d->resourceExactMatchesExcluded.insert(exactMatchCaptured); } } else { if (included) { d->resourceNamesPartsIncluded.append(part); } else { d->resourceNamesPartsExcluded.append(part); } } } } diff --git a/libs/resources/KisResourceSearchBoxFilter.h b/libs/resources/KisResourceSearchBoxFilter.h index d91f7e265d..8e7c6a3486 100644 --- a/libs/resources/KisResourceSearchBoxFilter.h +++ b/libs/resources/KisResourceSearchBoxFilter.h @@ -1,46 +1,51 @@ /* * Copyright (c) 2019 Agata Cacko * * 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_RESOURCE_SEARCH_BOX_FILTER_H #define KIS_RESOURCE_SEARCH_BOX_FILTER_H #include "kritaresources_export.h" #include +/** + * XXX: Apidox + * + */ class KRITARESOURCES_EXPORT KisResourceSearchBoxFilter { public: KisResourceSearchBoxFilter(); void setFilter(const QString& filter); bool matchesResource(const QString& resourceName); + bool isEmpty(); private: void initializeFilterData(); void clearFilterData(); class Private; Private * d; }; #endif diff --git a/libs/resources/KisResourceStorage.cpp b/libs/resources/KisResourceStorage.cpp index b00f001f6a..5eefc1f442 100644 --- a/libs/resources/KisResourceStorage.cpp +++ b/libs/resources/KisResourceStorage.cpp @@ -1,206 +1,211 @@ /* * Copyright (C) 2018 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 "KisResourceStorage.h" #include #include #include #include "KisFolderStorage.h" #include "KisBundleStorage.h" #include "KisMemoryStorage.h" const QString KisResourceStorage::s_meta_generator("meta:generator"); const QString KisResourceStorage::s_meta_author("dc:author"); const QString KisResourceStorage::s_meta_title("dc:title"); const QString KisResourceStorage::s_meta_description("dc:description"); const QString KisResourceStorage::s_meta_initial_creator("meta:initial-creator"); const QString KisResourceStorage::s_meta_creator("cd:creator"); const QString KisResourceStorage::s_meta_creation_date("meta:creation-data"); const QString KisResourceStorage::s_meta_dc_date("meta:dc-date"); const QString KisResourceStorage::s_meta_user_defined("meta:meta-userdefined"); const QString KisResourceStorage::s_meta_name("meta:name"); const QString KisResourceStorage::s_meta_value("meta:value"); const QString KisResourceStorage::s_meta_version("meta:bundle-version"); Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance); KisStoragePluginRegistry::KisStoragePluginRegistry() { m_storageFactoryMap[KisResourceStorage::StorageType::Folder] = new KisStoragePluginFactory(); m_storageFactoryMap[KisResourceStorage::StorageType::Memory] = new KisStoragePluginFactory(); m_storageFactoryMap[KisResourceStorage::StorageType::Bundle] = new KisStoragePluginFactory(); } void KisStoragePluginRegistry::addStoragePluginFactory(KisResourceStorage::StorageType storageType, KisStoragePluginFactoryBase *factory) { m_storageFactoryMap[storageType] = factory; } KisStoragePluginRegistry *KisStoragePluginRegistry::instance() { return s_instance; } class KisResourceStorage::Private { public: QString name; QString location; bool valid {false}; KisResourceStorage::StorageType storageType {KisResourceStorage::StorageType::Unknown}; QSharedPointer storagePlugin; }; KisResourceStorage::KisResourceStorage(const QString &location) : d(new Private()) { d->location = location; d->name = QFileInfo(d->location).fileName(); QFileInfo fi(d->location); if (fi.isDir()) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Folder]->create(location)); d->storageType = StorageType::Folder; d->valid = fi.isWritable(); } else if (d->location.endsWith(".bundle")) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Bundle]->create(location)); d->storageType = StorageType::Bundle; // XXX: should we also check whether there's a valid metadata entry? Or is this enough? d->valid = (fi.isReadable() && QuaZip(d->location).open(QuaZip::mdUnzip)); } else if (d->location.endsWith(".abr")) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::AdobeBrushLibrary]->create(location)); d->storageType = StorageType::AdobeBrushLibrary; d->valid = fi.isReadable(); } else if (d->location.endsWith(".asl")) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::AdobeStyleLibrary]->create(location)); d->storageType = StorageType::AdobeStyleLibrary; d->valid = fi.isReadable(); } else if (!d->location.isEmpty()) { d->storagePlugin.reset(KisStoragePluginRegistry::instance()->m_storageFactoryMap[StorageType::Memory]->create(location)); d->name = location; d->storageType = StorageType::Memory; d->valid = true; } } KisResourceStorage::~KisResourceStorage() { } KisResourceStorage::KisResourceStorage(const KisResourceStorage &rhs) : d(new Private) { *this = rhs; } KisResourceStorage &KisResourceStorage::operator=(const KisResourceStorage &rhs) { if (this != &rhs) { d->name = rhs.d->name; d->location = rhs.d->location; d->storageType = rhs.d->storageType; if (d->storageType == StorageType::Memory) { d->storagePlugin = QSharedPointer(new KisMemoryStorage(*dynamic_cast(rhs.d->storagePlugin.data()))); } else { d->storagePlugin = rhs.d->storagePlugin; } d->valid = false; } return *this; } KisResourceStorageSP KisResourceStorage::clone() const { return KisResourceStorageSP(new KisResourceStorage(*this)); } QString KisResourceStorage::name() const { return d->name; } QString KisResourceStorage::location() const { return d->location; } KisResourceStorage::StorageType KisResourceStorage::type() const { return d->storageType; } QImage KisResourceStorage::thumbnail() const { return d->storagePlugin->thumbnail(); } QDateTime KisResourceStorage::timestamp() const { return d->storagePlugin->timestamp(); } KisResourceStorage::ResourceItem KisResourceStorage::resourceItem(const QString &url) { return d->storagePlugin->resourceItem(url); } KoResourceSP KisResourceStorage::resource(const QString &url) { return d->storagePlugin->resource(url); } QSharedPointer KisResourceStorage::resources(const QString &resourceType) const { return d->storagePlugin->resources(resourceType); } QSharedPointer KisResourceStorage::tags(const QString &resourceType) const { return d->storagePlugin->tags(resourceType); } bool KisResourceStorage::addTag(const QString &resourceType, KisTagSP tag) { return d->storagePlugin->addTag(resourceType, tag); } bool KisResourceStorage::addResource(KoResourceSP resource) { return d->storagePlugin->addResource(resource->resourceType().first, resource); } +void KisResourceStorage::setMetaData(const QString &key, const QVariant &value) +{ + d->storagePlugin->setMetaData(key, value); +} + bool KisResourceStorage::valid() const { return d->valid; } QStringList KisResourceStorage::metaDataKeys() const { return d->storagePlugin->metaDataKeys(); } QVariant KisResourceStorage::metaData(const QString &key) const { return d->storagePlugin->metaData(key); } diff --git a/libs/resources/KisResourceStorage.h b/libs/resources/KisResourceStorage.h index fd6dd73f12..d7b8c2a49f 100644 --- a/libs/resources/KisResourceStorage.h +++ b/libs/resources/KisResourceStorage.h @@ -1,237 +1,262 @@ /* * Copyright (C) 2018 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 KISRESOURCESTORAGE_H #define KISRESOURCESTORAGE_H #include #include #include #include #include #include #include #include #include class KisStoragePlugin; class KRITARESOURCES_EXPORT KisStoragePluginFactoryBase { public: virtual ~KisStoragePluginFactoryBase(){} virtual KisStoragePlugin *create(const QString &/*location*/) { return 0; } }; template class KRITARESOURCES_EXPORT KisStoragePluginFactory : public KisStoragePluginFactoryBase { public: KisStoragePlugin *create(const QString &location) override { return new T(location); } }; class KisResourceStorage; typedef QSharedPointer KisResourceStorageSP; /** * The KisResourceStorage class is the base class for * places where resources can be stored. Examples are * folders, bundles or Adobe resource libraries like * ABR files. */ class KRITARESOURCES_EXPORT KisResourceStorage { public: /// A resource item is simply an entry in the storage, struct ResourceItem { virtual ~ResourceItem() {} QString url; QString folder; QString resourceType; QDateTime lastModified; }; class TagIterator { public: virtual ~TagIterator() {} virtual bool hasNext() const = 0; /// The iterator is only valid if next() has been called at least once. virtual void next() = 0; /// The untranslated name of the tag, to be used for making connections to resources virtual QString url() const = 0; /// The translated name of the tag, to be shown in the GUI virtual QString name() const = 0; /// An extra, optional comment for the tag virtual QString comment() const = 0; /// A tag object on which we can set properties and which we can save virtual KisTagSP tag() const = 0; }; class ResourceIterator { public: virtual ~ResourceIterator() {} virtual bool hasNext() const = 0; /// The iterator is only valid if next() has been called at least once. virtual void next() = 0; virtual QString url() const = 0; virtual QString type() const = 0; virtual QDateTime lastModified() const = 0; /// This only loads the resource when called virtual KoResourceSP resource() const = 0; }; enum class StorageType : int { Unknown = 1, Folder = 2, Bundle = 3, AdobeBrushLibrary = 4, AdobeStyleLibrary = 5, Memory = 6 }; static QString storageTypeToString(StorageType storageType) { switch (storageType) { case StorageType::Unknown: return i18n("Unknown"); case StorageType::Folder: return i18n("Folder"); case StorageType::Bundle: return i18n("Bundle"); case StorageType::AdobeBrushLibrary: return i18n("Adobe Brush Library"); case StorageType::AdobeStyleLibrary: return i18n("Adobe Style Library"); case StorageType::Memory: return i18n("Memory"); default: return i18n("Invalid"); } } + static QString storageTypeToUntranslatedString(StorageType storageType) { + switch (storageType) { + case StorageType::Unknown: + return ("Unknown"); + case StorageType::Folder: + return ("Folder"); + case StorageType::Bundle: + return ("Bundle"); + case StorageType::AdobeBrushLibrary: + return ("Adobe Brush Library"); + case StorageType::AdobeStyleLibrary: + return ("Adobe Style Library"); + case StorageType::Memory: + return ("Memory"); + default: + return ("Invalid"); + } + } + + KisResourceStorage(const QString &location); ~KisResourceStorage(); KisResourceStorage(const KisResourceStorage &rhs); KisResourceStorage &operator=(const KisResourceStorage &rhs); KisResourceStorageSP clone() const; /// The filename of the storage if it's a bundle or Adobe Library. This can /// also be empty (for the folder storage) or "memory" for the storage for /// temporary resources, a UUID for storages associated with documents. QString name() const; /// The absolute location of the storage QString location() const; /// true if the storage exists and can be used bool valid() const; /// The type of the storage StorageType type() const; /// The icond for the storage QImage thumbnail() const; /// The time and date when the storage was last modified, or created /// for memory storages. QDateTime timestamp() const; /// And entry in the storage; this is not the loaded resource ResourceItem resourceItem(const QString &url); /// The loaded resource for an entry in the storage KoResourceSP resource(const QString &url); /// An iterator over all the resources in the storage QSharedPointer resources(const QString &resourceType) const; /// An iterator over all the tags in the resource QSharedPointer tags(const QString &resourceType) const; /// Adds a tag to the storage, however, it does not store the links between /// tags and resources. bool addTag(const QString &resourceType, KisTagSP tag); /// Adds the given resource to the storage. bool addResource(KoResourceSP resource); static const QString s_meta_generator; static const QString s_meta_author; static const QString s_meta_title; static const QString s_meta_description; static const QString s_meta_initial_creator; static const QString s_meta_creator; static const QString s_meta_creation_date; static const QString s_meta_dc_date; static const QString s_meta_user_defined; static const QString s_meta_name; static const QString s_meta_value; static const QString s_meta_version; + void setMetaData(const QString &key, const QVariant &value); QStringList metaDataKeys() const; QVariant metaData(const QString &key) const; private: class Private; QScopedPointer d; }; inline QDebug operator<<(QDebug dbg, const KisResourceStorageSP storage) { - dbg.nospace() << "[RESOURCESTORAGE] Name: " << storage->name() - << " Version: " << storage->location() - << " Valid: " << storage->valid() - << " Storage: " << KisResourceStorage::storageTypeToString(storage->type()) - << " Timestamp: " << storage->timestamp(); - + if (storage.isNull()) { + dbg.nospace() << "[RESOURCESTORAGE] NULL"; + } + else { + dbg.nospace() << "[RESOURCESTORAGE] Name: " << storage->name() + << " Version: " << storage->location() + << " Valid: " << storage->valid() + << " Storage: " << KisResourceStorage::storageTypeToString(storage->type()) + << " Timestamp: " << storage->timestamp(); + } return dbg.space(); } class KRITARESOURCES_EXPORT KisStoragePluginRegistry { public: KisStoragePluginRegistry(); void addStoragePluginFactory(KisResourceStorage::StorageType storageType, KisStoragePluginFactoryBase *factory); static KisStoragePluginRegistry *instance(); private: friend class KisResourceStorage; QMap m_storageFactoryMap; }; #endif // KISRESOURCESTORAGE_H diff --git a/libs/resources/KisResourceTypeModel.h b/libs/resources/KisResourceTypeModel.h index 65859bf798..d3be6a5cce 100644 --- a/libs/resources/KisResourceTypeModel.h +++ b/libs/resources/KisResourceTypeModel.h @@ -1,54 +1,59 @@ /* * Copyright (c) 2018 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISRESOURCETYPEMODEL_H #define KISRESOURCETYPEMODEL_H #include #include #include #include "kritaresources_export.h" +/** + * KisResourceTypeModel provides a view on the various resource types + * defined in the database. This should be the same list as available + * from KisResourceLoaderRegistry. + */ class KRITARESOURCES_EXPORT KisResourceTypeModel : public QAbstractTableModel { Q_OBJECT public: enum Columns { Id = 0, ResourceType, Name, }; KisResourceTypeModel(QObject *parent = 0); ~KisResourceTypeModel() override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; private: bool prepareQuery(); struct Private; Private* const d; }; #endif // KISRESOURCETYPEMODEL_H diff --git a/libs/resources/KisStorageFilterProxyModel.cpp b/libs/resources/KisStorageFilterProxyModel.cpp new file mode 100644 index 0000000000..82210f9b11 --- /dev/null +++ b/libs/resources/KisStorageFilterProxyModel.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2018 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 "KisStorageFilterProxyModel.h" + +#include +#include +#include +#include +#include + +struct KisStorageFilterProxyModel::Private +{ + FilterType filterType {KisStorageFilterProxyModel::ByStorageType}; + QVariant filter; + +}; + +KisStorageFilterProxyModel::KisStorageFilterProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) + , d(new Private) +{ +} + +KisStorageFilterProxyModel::~KisStorageFilterProxyModel() +{ + delete d; +} + +KisResourceStorageSP KisStorageFilterProxyModel::storageForIndex(QModelIndex index) const +{ + KisStorageModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->storageForIndex(mapToSource(index)); + } + return 0; +} + +void KisStorageFilterProxyModel::setFilter(KisStorageFilterProxyModel::FilterType filterType, QVariant filter) +{ + d->filter = filter; + d->filterType = filterType; +} + + +bool KisStorageFilterProxyModel::filterAcceptsColumn(int /*source_column*/, const QModelIndex &/*source_parent*/) const +{ + return true; +} + +bool KisStorageFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + if (d->filter.isNull()) return true; + + QModelIndex idx = sourceModel()->index(source_row, KisResourceModel::Name, source_parent); + + switch (d->filterType) { + case ByFileName: + { + QMap v = d->filter.toMap(); + return KisResourceLocator::instance()->storageContainsResourceByFile(sourceModel()->data(idx, Qt::UserRole + KisStorageModel::Location).toString() + , v["resourcetype"].toString() + , v["filename"].toString()); + } + case ByStorageType: + { + QString storageType = sourceModel()->data(idx, Qt::UserRole + KisStorageModel::StorageType).toString(); + return (d->filter.toStringList().contains(storageType)); + } + default: + ; + } + + return false; +} + +bool KisStorageFilterProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const +{ + QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisResourceModel::Name).toString(); + QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisResourceModel::Name).toString(); + return nameLeft < nameRight; +} + +void KisStorageFilterProxyModel::slotModelReset() +{ + invalidateFilter(); +} diff --git a/libs/resources/KisActiveFilterTagProxyModel.h b/libs/resources/KisStorageFilterProxyModel.h similarity index 53% copy from libs/resources/KisActiveFilterTagProxyModel.h copy to libs/resources/KisStorageFilterProxyModel.h index 2fdff26605..80ce1e2006 100644 --- a/libs/resources/KisActiveFilterTagProxyModel.h +++ b/libs/resources/KisStorageFilterProxyModel.h @@ -1,50 +1,71 @@ /* - * Copyright (C) 2019 Agata Cacko + * Copyright (C) 2018 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 KIS_ACTIVE_FILTER_TAG_PROXY_MODEL_H -#define KIS_ACTIVE_FILTER_TAG_PROXY_MODEL_H - +#ifndef KISSTORAGEFILTERPROXYMODEL_H +#define KISSTORAGEFILTERPROXYMODEL_H #include #include -#include -#include - +#include +#include #include "kritaresources_export.h" -class KRITARESOURCES_EXPORT KisActiveFilterTagProxyModel : public QSortFilterProxyModel +/** + * KisStorageFilterProxyModel provides a filtered view on the available storages. + * It can be used to find the storages that have resource with a particular file + * name, or storages of particular types. + * + * Filtering by file name takes a string, filtering by storage type a list + * of untranslated strings (there is a method in KisResourceStorage for retrieving + * those strings from the ResourceType). + */ +class KRITARESOURCES_EXPORT KisStorageFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - KisActiveFilterTagProxyModel(QObject *parent); - ~KisActiveFilterTagProxyModel() override; + KisStorageFilterProxyModel(QObject *parent = 0); + ~KisStorageFilterProxyModel() override; + + enum FilterType { + ByFileName = 0, + ByStorageType + }; + + KisResourceStorageSP storageForIndex(QModelIndex index = QModelIndex()) const; + + void setFilter(FilterType filterType, QVariant filter); protected: bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override; bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; + +private Q_SLOTS: + void slotModelReset(); + + private: struct Private; Private *const d; - Q_DISABLE_COPY(KisActiveFilterTagProxyModel) + Q_DISABLE_COPY(KisStorageFilterProxyModel) }; -#endif // KIS_ACTIVE_FILTER_TAG_PROXY_MODEL_H +#endif diff --git a/libs/resources/KisStorageModel.cpp b/libs/resources/KisStorageModel.cpp index 76c6c81ad4..d71123a555 100644 --- a/libs/resources/KisStorageModel.cpp +++ b/libs/resources/KisStorageModel.cpp @@ -1,232 +1,277 @@ /* * Copyright (c) 2019 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisStorageModel.h" #include #include #include Q_GLOBAL_STATIC(KisStorageModel, s_instance) struct KisStorageModel::Private { int cachedRowCount {-1}; QSqlQuery query; }; KisStorageModel::KisStorageModel(QObject *parent) : QAbstractTableModel(parent) , d(new Private()) { prepareQuery(); } KisStorageModel *KisStorageModel::instance() { return s_instance; } KisStorageModel::~KisStorageModel() { } int KisStorageModel::rowCount(const QModelIndex & /*parent*/) const { if (d->cachedRowCount < 0) { QSqlQuery q; q.prepare("SELECT count(*)\n" "FROM storages\n"); q.exec(); q.first(); const_cast(this)->d->cachedRowCount = q.value(0).toInt(); } return d->cachedRowCount; } int KisStorageModel::columnCount(const QModelIndex &/*parent*/) const { - return 6; + return 8; } QVariant KisStorageModel::data(const QModelIndex &index, int role) const { QVariant v; + if (!index.isValid()) return v; if (index.row() > rowCount()) return v; - if (index.column() > (int)Active) return v; + if (index.column() > (int)MetaData) return v; bool pos = d->query.seek(index.row()); if (pos) { switch(role) { case Qt::DisplayRole: { switch(index.column()) { case Id: return d->query.value("id"); case StorageType: return d->query.value("storage_type"); case Location: return d->query.value("location"); case TimeStamp: return d->query.value("timestamp"); case PreInstalled: return d->query.value("pre_installed"); case Active: return d->query.value("active"); case Thumbnail: { QByteArray ba = d->query.value("thumbnail").toByteArray(); QBuffer buf(&ba); buf.open(QBuffer::ReadOnly); QImage img; img.load(&buf, "PNG"); return QVariant::fromValue(img); } + case DisplayName: + { + QMap r = KisResourceLocator::instance()->metaDataForStorage(d->query.value("location").toString()); + QVariant name = d->query.value("location"); + if (r.contains(KisResourceStorage::s_meta_name) && !r[KisResourceStorage::s_meta_name].isNull()) { + name = r[KisResourceStorage::s_meta_name]; + } + else if (r.contains(KisResourceStorage::s_meta_title) && !r[KisResourceStorage::s_meta_title].isNull()) { + name = r[KisResourceStorage::s_meta_title]; + } + return name; + } + case Qt::UserRole + MetaData: + { + QMap r = KisResourceLocator::instance()->metaDataForStorage(d->query.value("location").toString()); + return r; + } default: return v; } } case Qt::UserRole + Id: return d->query.value("id"); case Qt::UserRole + StorageType: return d->query.value("storage_type"); case Qt::UserRole + Location: return d->query.value("location"); case Qt::UserRole + TimeStamp: return d->query.value("timestamp"); case Qt::UserRole + PreInstalled: return d->query.value("pre_installed"); case Qt::UserRole + Active: return d->query.value("active"); case Qt::UserRole + Thumbnail: { QByteArray ba = d->query.value("thumbnail").toByteArray(); QBuffer buf(&ba); buf.open(QBuffer::ReadOnly); QImage img; img.load(&buf, "PNG"); return QVariant::fromValue(img); } + case Qt::UserRole + MetaData: + { + QMap r = KisResourceLocator::instance()->metaDataForStorage(d->query.value("location").toString()); + return r; + } + default: ; } } return v; } bool KisStorageModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.isValid()) { if (role == Qt::CheckStateRole) { QSqlQuery q; bool r = q.prepare("UPDATE storages\n" "SET active = :active\n" "WHERE id = :id\n"); q.bindValue(":active", value); q.bindValue(":id", index.data(Qt::UserRole + Id)); if (!r) { qWarning() << "Could not prepare KisStorageModel update query" << d->query.lastError(); return false; } r = q.exec(); if (!r) { qWarning() << "Could not execute KisStorageModel update query" << d->query.lastError(); return false; } } } QAbstractTableModel::setData(index, value, role); KisResourceModelProvider::resetAllModels(); return prepareQuery(); } Qt::ItemFlags KisStorageModel::flags(const QModelIndex &index) const { return QAbstractTableModel::flags(index) | Qt::ItemIsEditable; } -KisResourceStorageSP KisStorageModel::storageForId(const QModelIndex &index) const +KisResourceStorageSP KisStorageModel::storageForIndex(const QModelIndex &index) const { - return KisResourceLocator::instance()->storageByLocation(index.data(Qt::UserRole + Location).toString()); + return KisResourceLocator::instance()->storageByLocation(KisResourceLocator::instance()->makeStorageLocationAbsolute(index.data(Qt::UserRole + Location).toString())); } QVariant KisStorageModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant v = QVariant(); if (role != Qt::DisplayRole) { return v; } if (orientation == Qt::Horizontal) { switch(section) { case 0: v = i18n("Id"); break; case 1: v = i18n("Type"); break; case 2: v = i18n("Location"); break; case 3: v = i18n("Creation Date"); break; case 4: v = i18n("Preinstalled"); break; case 5: v = i18n("Active"); break; case 6: v = i18n("Thumbnail"); break; + case 7: + v = i18n("Name"); + break; default: v = QString::number(section); } return v; } return QAbstractTableModel::headerData(section, orientation, role); } +bool KisStorageModel::resetQuery() +{ + QTime t; + t.start(); + + beginResetModel(); + bool r = d->query.exec(); + if (!r) { + qWarning() << "Could not select storages" << d->query.lastError() << d->query.boundValues(); + } + d->cachedRowCount = -1; + + endResetModel(); + qDebug() << "KisStorageModel::resetQuery took" << t.elapsed() << "ms"; + + return r; +} + bool KisStorageModel::prepareQuery() { beginResetModel(); bool r = d->query.prepare("SELECT storages.id as id\n" ", storage_types.name as storage_type\n" ", location\n" ", timestamp\n" ", pre_installed\n" ", active\n" ", thumbnail\n" "FROM storages\n" ", storage_types\n" "WHERE storages.storage_type_id = storage_types.id\n"); if (!r) { qWarning() << "Could not prepare KisStorageModel query" << d->query.lastError(); } r = d->query.exec(); if (!r) { qWarning() << "Could not execute KisStorageModel query" << d->query.lastError(); } d->cachedRowCount = -1; endResetModel(); return r; } diff --git a/libs/resources/KisStorageModel.h b/libs/resources/KisStorageModel.h index 1c2021536f..1eea4d0592 100644 --- a/libs/resources/KisStorageModel.h +++ b/libs/resources/KisStorageModel.h @@ -1,70 +1,79 @@ /* * Copyright (c) 2019 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISSTORAGEMODEL_H #define KISSTORAGEMODEL_H #include #include #include #include "KisResourceStorage.h" #include "kritaresources_export.h" +/** + * KisStorageModel provides a model of all registered storages, like + * the folder storages, the bundle storages or the memory storages. Note + * that inactive storages are also part of this model. + */ class KRITARESOURCES_EXPORT KisStorageModel : public QAbstractTableModel { public: enum Columns { Id = 0, StorageType, Location, TimeStamp, PreInstalled, Active, - Thumbnail + Thumbnail, + DisplayName, + MetaData }; static KisStorageModel * instance(); KisStorageModel(QObject *parent = 0); ~KisStorageModel() override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; Qt::ItemFlags flags(const QModelIndex &index) const override; - KisResourceStorageSP storageForId(const QModelIndex &index) const; + KisResourceStorageSP storageForIndex(const QModelIndex &index) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + bool resetQuery(); + private: KisStorageModel(const KisStorageModel&); KisStorageModel operator=(const KisStorageModel&); bool prepareQuery(); struct Private; QScopedPointer d; }; #endif // KISSTORAGEMODEL_H diff --git a/libs/resources/KisStoragePlugin.h b/libs/resources/KisStoragePlugin.h index 0fa9b2583f..c90aec1478 100644 --- a/libs/resources/KisStoragePlugin.h +++ b/libs/resources/KisStoragePlugin.h @@ -1,65 +1,66 @@ /* * Copyright (C) 2018 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 KISSTORAGEPLUGIN_H #define KISSTORAGEPLUGIN_H #include #include #include #include "kritaresources_export.h" /** * The KisStoragePlugin class is the base class * for storage plugins. A storage plugin is used by * KisResourceStorage to locate resources and tags in * a kind of storage, like a folder, a bundle or an adobe * resource library. */ class KRITARESOURCES_EXPORT KisStoragePlugin { public: KisStoragePlugin(const QString &location); virtual ~KisStoragePlugin(); virtual KisResourceStorage::ResourceItem resourceItem(const QString &url) = 0; virtual KoResourceSP resource(const QString &url) = 0; virtual QSharedPointer resources(const QString &resourceType) = 0; virtual QSharedPointer tags(const QString &resourceType) = 0; virtual bool addTag(const QString &resourceType, KisTagSP tag) {Q_UNUSED(resourceType); Q_UNUSED(tag); return false;} virtual bool addResource(const QString &resourceType, KoResourceSP resource) {Q_UNUSED(resourceType); Q_UNUSED(resource); return false;} virtual QImage thumbnail() const { return QImage(); } + virtual void setMetaData(const QString &key, const QVariant &value) {Q_UNUSED(key); Q_UNUSED(value);} virtual QStringList metaDataKeys() const { return QStringList(); } - virtual QString metaData(const QString &key) const { Q_UNUSED(key); return QString(); } + virtual QVariant metaData(const QString &key) const { Q_UNUSED(key); return QString(); } QDateTime timestamp(); protected: friend class TestBundleStorage; QString location() const; private: class Private; QScopedPointer d; }; #endif // KISSTORAGEPLUGIN_H diff --git a/libs/resources/KisTagFilterResourceProxyModel.cpp b/libs/resources/KisTagFilterResourceProxyModel.cpp index c1d84af8a3..1be4fe2731 100644 --- a/libs/resources/KisTagFilterResourceProxyModel.cpp +++ b/libs/resources/KisTagFilterResourceProxyModel.cpp @@ -1,233 +1,224 @@ /* * Copyright (C) 2018 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 "KisTagFilterResourceProxyModel.h" #include #include #include #include struct KisTagFilterResourceProxyModel::Private { Private() : filter(new KisResourceSearchBoxFilter()) { } QList tags; KisTagModel* tagModel; QScopedPointer filter; + bool filterInCurrentTag; }; KisTagFilterResourceProxyModel::KisTagFilterResourceProxyModel(KisTagModel* model, QObject *parent) : QSortFilterProxyModel(parent) , d(new Private) { d->tagModel = model; //connect(model, SIGNAL(modelReset()), this, SLOT(slotModelReset())); } KisTagFilterResourceProxyModel::~KisTagFilterResourceProxyModel() { delete d; } KoResourceSP KisTagFilterResourceProxyModel::resourceForIndex(QModelIndex index) const { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->resourceForIndex(mapToSource(index)); } return 0; } QModelIndex KisTagFilterResourceProxyModel::indexFromResource(KoResourceSP resource) const { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return mapFromSource(source->indexFromResource(resource)); } return QModelIndex(); } bool KisTagFilterResourceProxyModel::removeResource(const QModelIndex &index) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->removeResource(mapToSource(index)); } return false; } bool KisTagFilterResourceProxyModel::importResourceFile(const QString &filename) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->importResourceFile(filename); } return false; } bool KisTagFilterResourceProxyModel::addResource(KoResourceSP resource, const QString &storageId) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->addResource(resource, storageId); } return false; } bool KisTagFilterResourceProxyModel::updateResource(KoResourceSP resource) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->updateResource(resource); } return false; } +bool KisTagFilterResourceProxyModel::renameResource(KoResourceSP resource, const QString &name) +{ + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->renameResource(resource, name); + } + return false; +} + bool KisTagFilterResourceProxyModel::removeResource(KoResourceSP resource) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->removeResource(resource); } return false; } bool KisTagFilterResourceProxyModel::setResourceMetaData(KoResourceSP resource, QMap metadata) { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->setResourceMetaData(resource, metadata); } return false; } void KisTagFilterResourceProxyModel::setTag(const KisTagSP tag) { - ENTER_FUNCTION() << (tag.isNull() ? "(null)" : tag->name()); - //d->tags = tag.split(QRegExp("[,]\\s*"), QString::SkipEmptyParts); d->tags.clear(); if (!tag.isNull()) { d->tags << tag; } invalidateFilter(); } void KisTagFilterResourceProxyModel::setSearchBoxText(const QString& seatchBoxText) { d->filter->setFilter(seatchBoxText); invalidateFilter(); } +void KisTagFilterResourceProxyModel::setFilterByCurrentTag(const bool filterInCurrentTag) +{ + d->filterInCurrentTag = filterInCurrentTag; + invalidateFilter(); +} + bool KisTagFilterResourceProxyModel::filterAcceptsColumn(int /*source_column*/, const QModelIndex &/*source_parent*/) const { return true; } +bool KisTagFilterResourceProxyModel::resourceHasCurrentTag(KisTagSP currentTag, QVector tagsForResource) const +{ + + if (!d->filterInCurrentTag && !d->filter->isEmpty()) { + // we don't need to check anything else because the user wants to search in all resources + // but if the filter text is empty, we do need to filter by the current tag + return true; + } + + if (currentTag.isNull()) { + // no tag set; all resources are allowed + return true; + } else { + if (currentTag->id() == KisTagModel::All) { + // current tag is "All", all resources are allowed + return true; + } else if (currentTag->id() == KisTagModel::AllUntagged) { + // current tag is "All untagged", all resources without any tags are allowed + return tagsForResource.size() == 0; + } else { + // checking whether the current tag is on the list of tags assigned to the resource + Q_FOREACH(KisTagSP temp, tagsForResource) { + if (temp->id() == currentTag->id()) { + return true; + } + } + } + } + return false; +} + bool KisTagFilterResourceProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { - //fprintf(stderr, "1 "); if (d->tagModel == 0) { return true; } - //fprintf(stderr, "2 "); + QModelIndex idx = sourceModel()->index(source_row, KisResourceModel::Name, source_parent); int resourceId = sourceModel()->data(idx, Qt::UserRole + KisResourceModel::Id).toInt(); QString resourceName = sourceModel()->data(idx, Qt::UserRole + KisResourceModel::Name).toString(); - //QStringList tags = sourceModel()->data(idx, Qt::UserRole + KisResourceModel::Tags).toStringList(); QVector tagsForResource = d->tagModel->tagsForResource(resourceId); - - //QString name = sourceModel()->data(idx, Qt::UserRole + KisResourceModel::Tags).toString(); - - - // TODO: RESOURCES: proper filtering by tag - //fprintf(stderr, "3 "); KisTagSP tag = d->tags.isEmpty() ? KisTagSP() : d->tags.first(); - bool hasCurrentTag = false; - //fprintf(stderr, "4 "); - //fprintf(stderr, "tag_first:_%s ", (tag.isNull() ? "(null)" : tag->name().toStdString().c_str())); - if (tag.isNull()) { - hasCurrentTag = true; - } - if (!hasCurrentTag && !tag.isNull()) { - if (tag->id() == KisTagModel::All) { - hasCurrentTag = true; - } else if (tag->id() == KisTagModel::AllUntagged) { - if (tagsForResource.size() == 0) { - hasCurrentTag = true; - } - } - } - //fprintf(stderr, "5-%d ", hasCurrentTag); - if (!hasCurrentTag && !tag.isNull()) { - Q_FOREACH(KisTagSP temp, tagsForResource) { - if (temp->url() == tag->url()) { - hasCurrentTag = true; - break; - } - } - } - //fprintf(stderr, "6-%d ", hasCurrentTag); - + bool hasCurrentTag = resourceHasCurrentTag(tag, tagsForResource); if (!hasCurrentTag) { - //fprintf(stderr, "end :( \n"); return false; } - //fprintf(stderr, "7-%d ", hasCurrentTag); bool currentFilterMatches = d->filter->matchesResource(resourceName); - //fprintf(stderr, "8-%d \n", hasCurrentTag); return currentFilterMatches; - - //sourceModel()->data(idx, ) - - /* - QSet tagResult = tags.toSet().subtract(tags.toSet()); - if (!tagResult.isEmpty()) { - return true; - } - - QString name = sourceModel()->data(idx, Qt::UserRole + KisResourceModel::Name).toString(); - Q_FOREACH(const KisTagSP &tag, d->tags) { - if (name.startsWith(tag)) { - return true; - } - } - */ - - return false; } bool KisTagFilterResourceProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisResourceModel::Name).toString(); QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisResourceModel::Name).toString(); return nameLeft < nameRight; } void KisTagFilterResourceProxyModel::slotModelReset() { invalidateFilter(); } diff --git a/libs/resources/KisTagFilterResourceProxyModel.h b/libs/resources/KisTagFilterResourceProxyModel.h index 6ecd390021..7585c7e608 100644 --- a/libs/resources/KisTagFilterResourceProxyModel.h +++ b/libs/resources/KisTagFilterResourceProxyModel.h @@ -1,76 +1,78 @@ /* * Copyright (C) 2018 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 KISTAGFILTERRESOURCEPROXYMODEL_H #define KISTAGFILTERRESOURCEPROXYMODEL_H #include #include #include #include #include #include #include "kritaresources_export.h" class KRITARESOURCES_EXPORT KisTagFilterResourceProxyModel : public QSortFilterProxyModel, public KisAbstractResourceModel { Q_OBJECT public: KisTagFilterResourceProxyModel(KisTagModel* model = 0, QObject *parent = 0); ~KisTagFilterResourceProxyModel() override; // KisAbstractResourceModel interface public: KoResourceSP resourceForIndex(QModelIndex index = QModelIndex()) const override; QModelIndex indexFromResource(KoResourceSP resource) const override; bool removeResource(const QModelIndex &index) override; bool importResourceFile(const QString &filename) override; bool addResource(KoResourceSP resource, const QString &storageId = QString()) override; bool updateResource(KoResourceSP resource) override; + bool renameResource(KoResourceSP resource, const QString &name) override; bool removeResource(KoResourceSP resource) override; bool setResourceMetaData(KoResourceSP resource, QMap metadata) override; /** * @brief setTag * @param tag */ void setTag(const KisTagSP tag); void setSearchBoxText(const QString& seatchBoxText); + void setFilterByCurrentTag(bool filterInCurrentTag); protected: - - bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override; bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; + bool resourceHasCurrentTag(KisTagSP currentTag, QVector tagsForResource) const; + private Q_SLOTS: void slotModelReset(); private: struct Private; Private *const d; Q_DISABLE_COPY(KisTagFilterResourceProxyModel) }; #endif // KISTAGFILTERRESOURCEPROXYMODEL_H diff --git a/libs/resources/KisTagModel.cpp b/libs/resources/KisTagModel.cpp index e327137cec..aa0e57fd9a 100644 --- a/libs/resources/KisTagModel.cpp +++ b/libs/resources/KisTagModel.cpp @@ -1,644 +1,606 @@ /* * Copyright (c) 2018 boud * Copyright (c) 2020 Agata Cacko * * 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 "KisTagModel.h" #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QSharedPointer) struct KisTagModel::Private { QSqlQuery query; QSqlQuery tagsForResourceQuery; QSqlQuery resourcesForTagQuery; QString resourceType; int columnCount {5}; int cachedRowCount {-1}; int fakeRowsCount {2}; }; KisTagModel::KisTagModel(const QString &resourceType, QObject *parent) : QAbstractTableModel(parent) , d(new Private()) { d->resourceType = resourceType; if (!d->resourceType.isEmpty()) { prepareQuery(); } } KisTagModel::~KisTagModel() { delete d; } int KisTagModel::rowCount(const QModelIndex &/*parent*/) const { if (d->cachedRowCount < 0) { QSqlQuery q; q.prepare("SELECT count(*)\n" "FROM tags\n" ", resource_types\n" "WHERE active = 1\n" "AND tags.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n"); q.bindValue(":resource_type", d->resourceType); q.exec(); q.first(); const_cast(this)->d->cachedRowCount = q.value(0).toInt() + d->fakeRowsCount; } return d->cachedRowCount; } int KisTagModel::columnCount(const QModelIndex &/*parent*/) const { return d->columnCount; } QVariant KisTagModel::data(const QModelIndex &index, int role) const { QVariant v; if (!index.isValid()) return v; if (index.row() > rowCount()) return v; if (index.column() > d->columnCount) return v; // The first row is All // XXX: Should we also add an "All Untagged"? if (index.row() < d->fakeRowsCount) { if (index.row() == KisTagModel::All + d->fakeRowsCount) { switch(role) { case Qt::DisplayRole: // fallthrough case Qt::ToolTipRole: // fallthrough case Qt::StatusTipRole: // fallthrough case Qt::WhatsThisRole: return i18n("All"); case Qt::UserRole + Id: return QString::number(KisTagModel::All); case Qt::UserRole + Url: { return "All"; } case Qt::UserRole + ResourceType: return d->resourceType; case Qt::UserRole + Active: return true; case Qt::UserRole + KisTagRole: { KisTagSP tag = tagForIndex(index); QVariant response; response.setValue(tag); return response; } default: ; } } else if (index.row() == KisTagModel::AllUntagged + d->fakeRowsCount) { switch(role) { case Qt::DisplayRole: // fallthrough case Qt::ToolTipRole: // fallthrough case Qt::StatusTipRole: // fallthrough case Qt::WhatsThisRole: return i18n("All untagged"); case Qt::UserRole + Id: return QString::number(KisTagModel::AllUntagged); case Qt::UserRole + Url: { return "All untagged"; } case Qt::UserRole + ResourceType: return d->resourceType; case Qt::UserRole + Active: return true; case Qt::UserRole + KisTagRole: { KisTagSP tag = tagForIndex(index); QVariant response; response.setValue(tag); return response; } default: ; } } } else { bool pos = const_cast(this)->d->query.seek(index.row() - d->fakeRowsCount); if (pos) { switch(role) { case Qt::DisplayRole: return d->query.value("name"); case Qt::ToolTipRole: // fallthrough case Qt::StatusTipRole: // fallthrough case Qt::WhatsThisRole: return d->query.value("comment"); case Qt::UserRole + Id: return d->query.value("id"); case Qt::UserRole + Url: return d->query.value("url"); case Qt::UserRole + ResourceType: return d->query.value("resource_type"); case Qt::UserRole + Active: return d->query.value("active"); case Qt::UserRole + KisTagRole: { KisTagSP tag = tagForIndex(index); QVariant response; response.setValue(tag); return response; } default: ; } } } return v; } void KisTagModel::setResourceType(const QString &resourceType) { d->resourceType = resourceType; prepareQuery(); } KisTagSP KisTagModel::tagForIndex(QModelIndex index) const { KisTagSP tag = 0; if (!index.isValid()) return tag; if (index.row() > rowCount()) return tag; if (index.column() > columnCount()) return tag; if (index.row() < d->fakeRowsCount) { if (index.row() == KisTagModel::All + d->fakeRowsCount) { tag.reset(new KisTag()); tag->setName(i18n("All")); tag->setUrl("All"); tag->setComment(i18n("All")); tag->setId(KisTagModel::All); tag->setActive(true); tag->setValid(true); } else if (index.row() == KisTagModel::AllUntagged + d->fakeRowsCount) { tag.reset(new KisTag()); tag->setName(i18n("All untagged")); tag->setUrl("All untagged"); tag->setComment(i18n("All untagged")); tag->setId(KisTagModel::AllUntagged); tag->setActive(true); tag->setValid(true); } } else { bool pos = const_cast(this)->d->query.seek(index.row() - d->fakeRowsCount); if (pos) { tag.reset(new KisTag()); tag->setUrl(d->query.value("url").toString()); tag->setName(d->query.value("name").toString()); tag->setComment(d->query.value("comment").toString()); tag->setId(d->query.value("id").toInt()); tag->setActive(d->query.value("active").toBool()); tag->setValid(true); } } return tag; } bool KisTagModel::addEmptyTag(const QString& tagName, QVector taggedResouces) { qDebug() << "bool KisTagModel::addEmptyTag(const QString& tagName, QVector taggedResouces) ### " << tagName; KisTagSP tag = KisTagSP(new KisTag()); tag->setName(tagName); tag->setUrl(tagName); return addEmptyTag(tag, taggedResouces); } bool KisTagModel::addEmptyTag(const KisTagSP tag, QVector taggedResouces) { qDebug() << "bool KisTagModel::addEmptyTag(const KisTagSP tag, QVector taggedResouces) ### " << tag; tag->setValid(true); tag->setActive(true); return addTag(tag, taggedResouces); } bool KisTagModel::addTag(const KisTagSP tag, QVector taggedResouces) { qDebug() << "######################"; qDebug() << "bool KisTagModel::addTag(const KisTagSP tag, QVector taggedResouces) " << tag; if (tag.isNull()) return false; if (!tag) return false; if (!tag->valid()) return false; // A new tag doesn't have an ID yet, that comes from the database if (tag->id() >= 0) return false; if (!KisResourceCacheDb::hasTag(tag->url(), d->resourceType)) { qDebug() << "it doesn't have the tag!" << tag->url() << tag->name() << tag->comment(); if (!KisResourceCacheDb::addTag(d->resourceType, "", tag->url(), tag->name(), tag->comment())) { qWarning() << "Could not add tag" << tag; return false; } } else { QSqlQuery q; if (!q.prepare("UPDATE tags\n" "SET active = 1\n" "WHERE url = :url\n" "AND resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type\n)")) { qWarning() << "Couild not prepare make existing tag active query" << tag << q.lastError(); return false; } q.bindValue(":url", tag->url()); q.bindValue(":resource_type", d->resourceType); if (!q.exec()) { qWarning() << "Couild not execute make existing tag active query" << q.boundValues(), q.lastError(); return false; } } qDebug() << "tag = " << tag; if (!taggedResouces.isEmpty()) { qDebug() << "********************"; qDebug() << "tag = " << tag; qDebug() << "tag url = " << tag->url(); KisTagSP tagFromDb = tagByUrl(tag->url()); qDebug() << "tag from db: " << tagFromDb << tag->id(); Q_FOREACH(const KoResourceSP resource, taggedResouces) { if (!resource) continue; if (!resource->valid()) continue; if (resource->resourceId() < 0) continue; tagResource(tagFromDb, resource); } } qDebug() << "^^^^^^^^^^^^^^^^^^^^^^^^^"; return prepareQuery(); } bool KisTagModel::removeTag(const KisTagSP tag) { if (!tag) return false; if (!tag->valid()) return false; if (tag->id() < 0) return false; QSqlQuery q; if (!q.prepare("UPDATE tags\n" "SET active = 0\n" "WHERE id = :id")) { qWarning() << "Could not prepare remove tag query" << q.lastError(); return false; } q.bindValue(":id", tag->id()); if (!q.exec()) { qWarning() << "Could not execute remove tag query" << q.lastError(); return false; } return prepareQuery(); } bool KisTagModel::tagResource(const KisTagSP tag, const KoResourceSP resource) { if (!tag) return false; if (!tag->valid()) return false; if (tag->id() < 0) return false; qDebug() << tag << " tag id " << tag->id(); if (!resource) return false; if (!resource->valid()) return false; if (resource->resourceId() < 0) return false; QSqlQuery q; bool r = q.prepare("INSERT INTO resource_tags\n" "(resource_id, tag_id)\n" "VALUES\n" "( (SELECT id\n" " FROM resources\n" " WHERE id = :resource_id)\n" ", (SELECT id\n" " FROM tags\n" " WHERE id = :tag_id\n" " AND resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type" " \n)" " )\n" ")\n"); if (!r) { qWarning() << "Could not prepare insert into resource tags statement" << q.lastError(); return false; } q.bindValue(":resource_id", resource->resourceId()); q.bindValue(":tag_id", tag->id()); q.bindValue(":resource_type", d->resourceType); if (!q.exec()) { qWarning() << "Could not execute insert into resource tags statement" << q.boundValues() << q.lastError(); return false; } KisResourceModelProvider::resetModel(d->resourceType); return true; } bool KisTagModel::untagResource(const KisTagSP tag, const KoResourceSP resource) { if (!tag) return false; if (!tag->valid()) return false; if (!tag->id()) return false; if (!resource) return false; if (!resource->valid()) return false; if (resource->resourceId() < 0) return false; // we need to delete an entry in resource_tags QSqlQuery query; bool r = query.prepare("DELETE FROM resource_tags\n" "WHERE resource_id = :resource_id\n" "AND tag_id = :tag_id"); if (!r) { qWarning() << "Could not prepare KisTagModel query untagResource " << query.lastError(); } query.bindValue(":resource_id", resource->resourceId()); query.bindValue(":tag_id", tag->id()); r = query.exec(); if (!r) { qWarning() << "Could not select tags" << query.lastError(); } KisResourceModelProvider::resetModel(d->resourceType); return true; } bool KisTagModel::renameTag(const KisTagSP tag, const QString &name) { if (!tag) return false; if (!tag->valid()) return false; if (name.isEmpty()) return false; QSqlQuery q; if (!q.prepare("UPDATE tags\n" "SET name = :name\n" "WHERE url = :url\n" "AND resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type\n)")) { qWarning() << "Couild not prepare make existing tag active query" << tag << q.lastError(); return false; } q.bindValue(":name", name); q.bindValue(":url", tag->url()); q.bindValue(":resource_type", d->resourceType); if (!q.exec()) { qWarning() << "Couild not execute make existing tag active query" << q.boundValues(), q.lastError(); return false; } return prepareQuery(); } bool KisTagModel::changeTagActive(const KisTagSP tag, bool active) { if (!tag) return false; if (!tag->valid()) return false; QSqlQuery q; if (!q.prepare("UPDATE tags\n" "SET active = :active\n" "WHERE url = :url\n" "AND resource_type_id = (SELECT id\n" " FROM resource_types\n" " WHERE name = :resource_type\n)")) { qWarning() << "Couild not prepare make existing tag active query" << tag << q.lastError(); return false; } q.bindValue(":active", active); q.bindValue(":url", tag->url()); q.bindValue(":resource_type", d->resourceType); if (!q.exec()) { qWarning() << "Couild not execute make existing tag active query" << q.boundValues(), q.lastError(); return false; } return prepareQuery(); } QVector KisTagModel::tagsForResource(int resourceId) const { bool r = d->tagsForResourceQuery.prepare("SELECT tags.id\n" ", tags.url\n" ", tags.name\n" ", tags.comment\n" "FROM tags\n" ", resource_tags\n" "WHERE tags.active > 0\n" // make sure the tag is active "AND tags.id = resource_tags.tag_id\n" // join tags + resource_tags by tag_id "AND resource_tags.resource_id = :resource_id\n"); // make sure we're looking for tags for a specific resource if (!r) { qWarning() << "Could not prepare TagsForResource query" << d->tagsForResourceQuery.lastError(); } d->tagsForResourceQuery.bindValue(":resource_id", resourceId); r = d->tagsForResourceQuery.exec(); if (!r) { qWarning() << "Could not select tags for" << resourceId << d->tagsForResourceQuery.lastError() << d->tagsForResourceQuery.boundValues(); } QVector tags; while (d->tagsForResourceQuery.next()) { //qDebug() << d->tagQuery.value(0).toString() << d->tagQuery.value(1).toString() << d->tagQuery.value(2).toString(); KisTagSP tag(new KisTag()); tag->setId(d->tagsForResourceQuery.value("id").toInt()); tag->setUrl(d->tagsForResourceQuery.value("url").toString()); tag->setName(d->tagsForResourceQuery.value("name").toString()); tag->setComment(d->tagsForResourceQuery.value("comment").toString()); tag->setValid(true); tag->setActive(true); tags << tag; } return tags; } -QVector KisTagModel::resourcesForTag(int tagId) const -{ - bool r = d->resourcesForTagQuery.prepare("SELECT DISTINCT resources.id\n" - "FROM resources\n" - ", resource_tags\n" - ", storages\n" - ", resource_types\n" - "WHERE resources.id = resource_tags.resource_id\n" // join resources + resource_tags by resource_id - "AND resources.resource_type_id = resource_types.id\n" // join with resource types via resource type id - "AND resources.storage_id = storages.id\n" // join with storages via storage id - "AND resource_tags.tag_id = :tag_id\n" // must have the tag - "AND resource_types.name = :resource_type\n" // the type must match the current type - "AND resources.status = 1\n" // must be active itself - "AND storages.active = 1"); // must be from active storage - if (!r) { - qWarning() << "Could not prepare ResourcesForTag query" << d->resourcesForTagQuery.lastError(); - } - - d->resourcesForTagQuery.bindValue(":tag_id", tagId); - d->resourcesForTagQuery.bindValue(":resource_type", d->resourceType); - - r = d->resourcesForTagQuery.exec(); - if (!r) { - qWarning() << "Could not select resources for tag " << tagId << d->resourcesForTagQuery.lastError() << d->resourcesForTagQuery.boundValues(); - } - - QVector resources; - qDebug() << "it's around: " << d->resourcesForTagQuery.size() << " resources for " << d->resourceType; - while (d->resourcesForTagQuery.next()) { - qDebug() << "KisTagModel::resourcesForTag(int tagId): ### query: " << d->resourcesForTagQuery.value("id").toInt(); - KoResourceSP resource = KisResourceLocator::instance()->resourceForId(d->resourcesForTagQuery.value("id").toInt()); - resources << resource; - } - - return resources; - -} - KisTagSP KisTagModel::tagByUrl(const QString& tagUrl) const { if (tagUrl.isEmpty()) { return KisTagSP(); } QSqlQuery query; bool r = query.prepare("SELECT tags.id\n" ", tags.url\n" ", tags.name\n" ", tags.comment\n" ", tags.active\n" ", resource_types.name as resource_type\n" "FROM tags\n" ", resource_types\n" "WHERE tags.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n" "AND tags.active = 1\n" "AND tags.url = :tag_url\n" "AND tags.storage_id in (SELECT id\n" " FROM storages\n" " WHERE storages.active == 1)"); if (!r) { qWarning() << "Could not prepare KisTagModel::tagByUrl query" << query.lastError(); } query.bindValue(":resource_type", d->resourceType); QString tagUrlForSql = tagUrl; query.bindValue(":tag_url", tagUrlForSql); r = query.exec(); if (!r) { qWarning() << "Could not execute KisTagModel::tagByUrl query" << query.lastError(); } KisTagSP tag(new KisTag()); query.next(); tag->setUrl(query.value("url").toString()); tag->setName(query.value("name").toString()); tag->setComment(query.value("comment").toString()); tag->setId(query.value("id").toInt()); tag->setActive(query.value("active").toBool()); tag->setValid(true); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(tagUrl == tag->url(), KisTagSP()); return tag; } bool KisTagModel::prepareQuery() { QTime t; t.start(); beginResetModel(); bool r = d->query.prepare("SELECT tags.id\n" ", tags.url\n" ", tags.name\n" ", tags.comment\n" ", tags.active\n" ", resource_types.name as resource_type\n" "FROM tags\n" ", resource_types\n" "WHERE tags.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type\n" "AND tags.active = 1\n"); if (!r) { qWarning() << "Could not prepare KisTagModel query" << d->query.lastError(); } d->query.bindValue(":resource_type", d->resourceType); r = d->query.exec(); if (!r) { qWarning() << "Could not select tags" << d->query.lastError(); } d->cachedRowCount = -1; endResetModel(); qDebug() << "bool KisTagModel::prepareQuery() ### RESET TAG MODEL ### for " << d->resourceType << " took " << t.elapsed() << " ms."; return r; } diff --git a/libs/resources/KisTagModel.h b/libs/resources/KisTagModel.h index 010c4d5fd9..a663329137 100644 --- a/libs/resources/KisTagModel.h +++ b/libs/resources/KisTagModel.h @@ -1,105 +1,104 @@ /* * Copyright (c) 2018 boud * Copyright (c) 2020 Agata Cacko * * 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 KISTAGMODEL_H #define KISTAGMODEL_H #include #include #include #include #include "kritaresources_export.h" class KRITARESOURCES_EXPORT KisTagModel : public QAbstractTableModel { Q_OBJECT private: KisTagModel(const QString &resourceType, QObject *parent = 0); public: enum Columns { Id = 0, Url, Name, Comment, ResourceType, Active, KisTagRole, }; enum Ids { All = -2, // so it gets on top in the combobox AllUntagged = -1, }; ~KisTagModel() override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; // KisTagModel API KisTagSP tagForIndex(QModelIndex index = QModelIndex()) const; QList allTags() const; bool addEmptyTag(const QString &tagName, QVector taggedResouces); bool addEmptyTag(const KisTagSP tag, QVector taggedResouces); bool addTag(const KisTagSP tag, QVector taggedResouces = QVector()); bool removeTag(const KisTagSP tag); bool tagResource(const KisTagSP tag, const KoResourceSP resource); bool untagResource(const KisTagSP tag, const KoResourceSP resource); bool renameTag(const KisTagSP tag, const QString &name); bool changeTagActive(const KisTagSP tag, bool active); QVector tagsForResource(int resourceId) const; - QVector resourcesForTag(int tagId) const; KisTagSP tagByUrl(const QString& tagUrl) const; private: friend class DlgDbExplorer; friend class KisTagModelProvider; friend class TestTagModel; void setResourceType(const QString &resourceType); bool tagResourceByUrl(const QString& tagUrl, const int resourceId); bool tagResourceById(const int tagId, const int resource); bool prepareQuery(); struct Private; Private* const d; }; typedef QSharedPointer KisTagModelSP; #endif // KISTAGMODEL_H diff --git a/libs/resources/KoHashGenerator.h b/libs/resources/KoHashGenerator.h deleted file mode 100644 index 383ff9bc15..0000000000 --- a/libs/resources/KoHashGenerator.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2015 Stefano Bonicatti - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. -*/ -#ifndef KOHASHGENERATOR_H -#define KOHASHGENERATOR_H - -#include -#include - -class KoHashGenerator -{ -public: - virtual QByteArray generateHash(const QString &filename) = 0; - virtual QByteArray generateHash(const QByteArray &array) = 0; - virtual ~KoHashGenerator(){} -}; -#endif diff --git a/libs/resources/KoHashGeneratorProvider.cpp b/libs/resources/KoHashGeneratorProvider.cpp deleted file mode 100644 index 8dbfcad073..0000000000 --- a/libs/resources/KoHashGeneratorProvider.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2015 Stefano Bonicatti - * - * 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 "KoHashGeneratorProvider.h" - -#include -#include - -#include "KoMD5Generator.h" - -KoHashGeneratorProvider *KoHashGeneratorProvider::instance_var = 0; -Q_GLOBAL_STATIC(KoHashGeneratorProvider, s_instance) - -KoHashGeneratorProvider::KoHashGeneratorProvider() -{ - // Initialize default generators - hashGenerators.insert("MD5", new KoMD5Generator()); -} - -KoHashGeneratorProvider::~KoHashGeneratorProvider() -{ - qDeleteAll(hashGenerators); -} - -KoHashGenerator *KoHashGeneratorProvider::getGenerator(const QString &algorithm) -{ - QMutexLocker locker(&mutex); - return hashGenerators.value(algorithm); -} - -void KoHashGeneratorProvider::setGenerator(const QString &algorithm, KoHashGenerator *generator) -{ - if (hashGenerators.contains(algorithm)) { - delete hashGenerators.take(algorithm); - hashGenerators[algorithm] = generator; - } - else - hashGenerators.insert(algorithm, generator); -} - -KoHashGeneratorProvider *KoHashGeneratorProvider::instance() -{ - return s_instance; -} diff --git a/libs/resources/KoHashGeneratorProvider.h b/libs/resources/KoHashGeneratorProvider.h deleted file mode 100644 index ae97e5699d..0000000000 --- a/libs/resources/KoHashGeneratorProvider.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2015 Stefano Bonicatti - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. -*/ -#ifndef KOHASHGENERATORPROVIDER_H -#define KOHASHGENERATORPROVIDER_H - -#include -#include - -#include - -class KoHashGenerator; - -class KRITARESOURCES_EXPORT KoHashGeneratorProvider -{ -public: - KoHashGeneratorProvider(); - ~KoHashGeneratorProvider(); - - KoHashGenerator *getGenerator(const QString &algorithm); - void setGenerator(const QString &algorithm, KoHashGenerator *generator); - static KoHashGeneratorProvider *instance(); -private: - static KoHashGeneratorProvider *instance_var; - QHash hashGenerators; - QMutex mutex; -}; - -#endif diff --git a/libs/resources/KoMD5Generator.cpp b/libs/resources/KoMD5Generator.cpp index 3c65fadde9..4cab50aa35 100644 --- a/libs/resources/KoMD5Generator.cpp +++ b/libs/resources/KoMD5Generator.cpp @@ -1,57 +1,47 @@ /* * Copyright (c) 2015 Stefano Bonicatti * * 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 "KoMD5Generator.h" #include #include #include -KoMD5Generator::KoMD5Generator() -{ - -} - -KoMD5Generator::~KoMD5Generator() -{ - -} - QByteArray KoMD5Generator::generateHash(const QByteArray &array) { if (!array.isEmpty()) { QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(array); return md5.result(); } return array; } QByteArray KoMD5Generator::generateHash(const QString &filename) { QByteArray result; QFile f(filename); if (f.exists() && f.open(QIODevice::ReadOnly)) { QByteArray ba = f.readAll(); result = generateHash(ba); } return result; } diff --git a/libs/resources/KoMD5Generator.h b/libs/resources/KoMD5Generator.h index 6d9cd7d256..1125eaf0e0 100644 --- a/libs/resources/KoMD5Generator.h +++ b/libs/resources/KoMD5Generator.h @@ -1,35 +1,34 @@ -/* +#/* * Copyright (c) 2015 Stefano Bonicatti * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOMD5GENERATOR_H #define KOMD5GENERATOR_H -#include "KoHashGenerator.h" +#include +#include #include -class KRITARESOURCES_EXPORT KoMD5Generator : public KoHashGenerator +class KRITARESOURCES_EXPORT KoMD5Generator { public: - KoMD5Generator(); - ~KoMD5Generator() override; - QByteArray generateHash(const QString &filename) override; - QByteArray generateHash(const QByteArray &array) override; + static QByteArray generateHash(const QString &filename); + static QByteArray generateHash(const QByteArray &array); }; #endif diff --git a/libs/resources/KoResource.cpp b/libs/resources/KoResource.cpp index 6e3d275bbe..aebd224a99 100644 --- a/libs/resources/KoResource.cpp +++ b/libs/resources/KoResource.cpp @@ -1,266 +1,274 @@ /* This file is part of the KDE project Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Boudewijn Rempt Copyright (c) 2007 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include -#include "KoHashGenerator.h" -#include "KoHashGeneratorProvider.h" #include +#include "KoMD5Generator.h" + struct Q_DECL_HIDDEN KoResource::Private { int version {0}; int resourceId {-1}; bool valid {false}; bool permanent {false}; bool dirty {false}; QString name; QString filename; QString storageLocation; QByteArray md5; QImage image; QMap metadata; }; KoResource::KoResource(const QString& filename) : d(new Private) { d->filename = filename; } KoResource::~KoResource() { delete d; } KoResource::KoResource(const KoResource &rhs) : d(new Private) { *this = rhs; } KoResource &KoResource::operator=(const KoResource &rhs) { if (this != &rhs) { d->name = rhs.d->name; d->filename= rhs.d->filename; d->valid = rhs.d->valid; d->md5 = rhs.d->md5; d->image = rhs.d->image; d->permanent = rhs.d->permanent; d->resourceId = rhs.d->resourceId; d->storageLocation = rhs.d->storageLocation; d->dirty = rhs.d->dirty; d->metadata = rhs.d->metadata; d->version = rhs.d->version; } return *this; } bool KoResource::load(KisResourcesInterfaceSP resourcesInterface) { QFile file(filename()); if (!file.exists()) { warnKrita << "File doesn't exist: " << filename(); return false; } if (file.size() == 0) { warnKrita << "File is empty: " << filename(); return false; } if (!file.open(QIODevice::ReadOnly)) { warnKrita << "Can't open file for reading" << filename(); return false; } const bool res = loadFromDevice(&file, resourcesInterface); file.close(); return res; } bool KoResource::save() { if (filename().isEmpty()) return false; QFile file(filename()); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { warnKrita << "Can't open file for writing" << filename(); return false; } saveToDevice(&file); file.close(); return true; } bool KoResource::saveToDevice(QIODevice *dev) const { Q_UNUSED(dev) d->md5 = QByteArray(); return true; } QImage KoResource::image() const { return d->image; } +void KoResource::updateThumbnail() +{ +} + +QImage KoResource::thumbnail() const +{ + return image(); +} + void KoResource::setImage(const QImage &image) { d->image = image; } QByteArray KoResource::md5() const { if (d->md5.isEmpty()) { const_cast(this)->setMD5(generateMD5()); } return d->md5; } void KoResource::setMD5(const QByteArray &md5) { d->md5 = md5; } QByteArray KoResource::generateMD5() const { - KoHashGenerator *hashGenerator = KoHashGeneratorProvider::instance()->getGenerator("MD5"); QByteArray hash; QByteArray ba; QBuffer buf(&ba); buf.open(QBuffer::WriteOnly); if (saveToDevice(&buf)) { buf.close(); - hash = hashGenerator->generateHash(ba); + hash = KoMD5Generator::generateHash(ba); } else { qWarning() << "Could not create md5sum for resource" << d->filename; } return hash; } QString KoResource::filename() const { return d->filename; } void KoResource::setFilename(const QString& filename) { d->filename = filename; } QString KoResource::name() const { return (!d->name.isEmpty() ? d->name : QFileInfo(filename()).fileName()); } void KoResource::setName(const QString& name) { d->name = name; } bool KoResource::valid() const { return d->valid; } void KoResource::setValid(bool valid) { d->valid = valid; } QString KoResource::defaultFileExtension() const { return QString(); } bool KoResource::permanent() const { return d->permanent; } void KoResource::setPermanent(bool permanent) { d->permanent = permanent; } int KoResource::resourceId() const { return d->resourceId; } QString KoResource::storageLocation() const { return d->storageLocation; } void KoResource::setDirty(bool value) { d->dirty = value; } bool KoResource::isDirty() const { return d->dirty; } void KoResource::addMetaData(QString key, QVariant value) { d->metadata.insert(key, value); } QMap KoResource::metadata() const { return d->metadata; } int KoResource::version() const { return d->version; } void KoResource::setVersion(int version) { d->version = version; } void KoResource::setResourceId(int id) { d->resourceId = id; } void KoResource::setStorageLocation(const QString &location) { d->storageLocation = location; } diff --git a/libs/resources/KoResource.h b/libs/resources/KoResource.h index cafcc5fe1c..373a7b7f79 100644 --- a/libs/resources/KoResource.h +++ b/libs/resources/KoResource.h @@ -1,202 +1,218 @@ /* This file is part of the KDE project Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KORESOURCE_H #define KORESOURCE_H #include #include #include #include #include #include "KisResourceTypes.h" #include class QDomDocument; class QDomElement; class KoResource; typedef QSharedPointer KoResourceSP; class KisResourcesInterface; typedef QSharedPointer KisResourcesInterfaceSP; /** * The KoResource class provides a representation of resources. This * includes, but not limited to, brushes and patterns. * * A resource knows its filename, but not the location where it's stored. * A new version of a resource is stored with an updated filename, the old * version isn't renamed. * */ class KRITARESOURCES_EXPORT KoResource { public: /** * Creates a new KoResource object using @p filename. No file is opened * in the constructor, you have to call load. * * @param filename the file name to save and load from. */ explicit KoResource(const QString &filename); virtual ~KoResource(); KoResource(const KoResource &rhs); KoResource &operator=(const KoResource &rhs); virtual KoResourceSP clone() const = 0; bool operator !=(const KoResource &other) const { return &other != this; } bool operator ==(const KoResource &other) const { return other.md5() == md5(); } public: /** * Load this resource. * @return true if loading the resource succeeded. */ virtual bool load(KisResourcesInterfaceSP resourcesInterface); virtual bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) = 0; /** * Save this resource. *@return true if saving the resource succeeded. */ virtual bool save(); virtual bool saveToDevice(QIODevice* dev) const; /** - * @returns a QImage thumbnail image representing this resource. + * @returns a QImage image representing this resource: in the case + * of some resources, it is the actual resource. * * This image could be null. The image can be in any valid format. */ QImage image() const; void setImage(const QImage &image); + /** + * @brief updateThumbnail updates the thumbnail for this resource. + * Reimplement if your thumbnail is something else than the image + * set with setImage. + */ + virtual void updateThumbnail(); + + /** + * @brief thumbnail the thumbnail image to use in resource selectors + * @return a valid qimage. All thumbnails for a given resource have the + * same size (which is not true for image(), but that size need not + * be square. By default it's the same as image(), but that is not guaranteed. + */ + virtual QImage thumbnail() const; + /// @return the md5sum calculated over the contents of the resource. QByteArray md5() const; /// @return the unique identifier of this resource within the container (folder, bundle, ...) QString filename() const; void setFilename(const QString& filename); /// @return the user-visible name of the resource QString name() const; void setName(const QString& name); /// @return true if the resource is ready for use bool valid() const; void setValid(bool valid); /// @return the default file extension which should be used when saving the resource virtual QString defaultFileExtension() const; /// @return true if the resource is permanent and can't be removed by the user bool permanent() const; void setPermanent(bool permanent); /// @return the name of the storage location of the resource QString storageLocation() const; /// Mark the preset as modified but not saved void setDirty(bool value); /// @return true if the preset has been modified, but not saved bool isDirty() const; /// store the given key, value pair in the resource void addMetaData(QString key, QVariant value); /// get a map with all the metadata QMap metadata() const; /// Get the version of the resource int version() const; /// @return the unique id of the resource in the resource database int resourceId() const; /// @return the resource type virtual QPair resourceType() const = 0; private: friend class KisResourceModel; friend class KisResourceLocator; friend class TestResourceModel; friend class TestResourceLocator; friend class TestFolderStorage; friend class KisFolderStorage; friend class KisMemoryStorage; void setVersion(int version); void setResourceId(int id); void setStorageLocation(const QString &location); protected: /// override generateMD5 and in your resource subclass virtual QByteArray generateMD5() const; /// call this when the contents of the resource change so the md5 needs to be recalculated void setMD5(const QByteArray &md5); private: struct Private; Private* const d; }; static inline bool operator==(const KoResource &resource1, const KoResource &resource2) { return (resource1.md5() == resource2.md5()); } static inline uint qHash(const KoResource &resource) { return qHash(resource.md5()); } Q_DECLARE_METATYPE(QSharedPointer) inline QDebug operator<<(QDebug dbg, const KoResourceSP res) { if (!res) { dbg.noquote() << "NULL Resource"; } else { dbg.nospace() << "[RESOURCE] Name: " << res->name() << " Version: " << res->version() << " Filename: " << res->filename() << " Valid: " << res->valid() << " Storage: " << res->storageLocation(); } return dbg.space(); } #endif // KORESOURCE_H_ diff --git a/libs/resources/KoResourceBundle.cpp b/libs/resources/KoResourceBundle.cpp index ec83b7cc0d..f48e49083b 100644 --- a/libs/resources/KoResourceBundle.cpp +++ b/libs/resources/KoResourceBundle.cpp @@ -1,539 +1,538 @@ /* * Copyright (c) 2014 Victor Lafon metabolic.ewilan@hotmail.fr * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoResourceBundle.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KoResourceBundleManifest.h" -#include -#include +#include #include #include #include #include #include "KisStoragePlugin.h" #include "KisResourceLoaderRegistry.h" #include #include #include KoResourceBundle::KoResourceBundle(QString const& fileName) : m_filename(fileName), m_bundleVersion("1") { m_metadata[KisResourceStorage::s_meta_generator] = "Krita (" + KritaVersionWrapper::versionString(true) + ")"; } KoResourceBundle::~KoResourceBundle() { } QString KoResourceBundle::defaultFileExtension() const { return QString(".bundle"); } bool KoResourceBundle::load() { if (m_filename.isEmpty()) return false; QScopedPointer resourceStore(KoStore::createStore(m_filename, KoStore::Read, "application/x-krita-resourcebundle", KoStore::Zip)); if (!resourceStore || resourceStore->bad()) { qWarning() << "Could not open store on bundle" << m_filename; return false; } else { m_metadata.clear(); if (resourceStore->open("META-INF/manifest.xml")) { if (!m_manifest.load(resourceStore->device())) { qWarning() << "Could not open manifest for bundle" << m_filename; return false; } resourceStore->close(); Q_FOREACH (KoResourceBundleManifest::ResourceReference ref, m_manifest.files()) { if (!resourceStore->open(ref.resourcePath)) { qWarning() << "Bundle is broken. File" << ref.resourcePath << "is missing"; } else { resourceStore->close(); } } } else { qWarning() << "Could not load META-INF/manifest.xml"; return false; } bool versionFound = false; if (!readMetaData(resourceStore.data())) { qWarning() << "Could not load meta.xml"; return false; } if (resourceStore->open("preview.png")) { // Workaround for some OS (Debian, Ubuntu), where loading directly from the QIODevice // fails with "libpng error: IDAT: CRC error" QByteArray data = resourceStore->device()->readAll(); QBuffer buffer(&data); m_thumbnail.load(&buffer, "PNG"); resourceStore->close(); } else { qWarning() << "Could not open preview.png"; } /* * If no version is found it's an old bundle with md5 hashes to fix, or if some manifest resource entry * doesn't not correspond to a file the bundle is "broken", in both cases we need to recreate the bundle. */ if (!versionFound) { m_metadata.insert(KisResourceStorage::s_meta_version, "1"); } } return true; } bool KoResourceBundle::loadFromDevice(QIODevice *) { return false; } bool saveResourceToStore(KoResourceSP resource, KoStore *store, const QString &resType) { if (!resource) { qWarning() << "No Resource"; return false; } if (!resource->valid()) { qWarning() << "Resource is not valid"; return false; } if (!store || store->bad()) { qWarning() << "No Store or Store is Bad"; return false; } QByteArray ba; QBuffer buf; QFileInfo fi(resource->filename()); if (fi.exists() && fi.isReadable()) { QFile f(resource->filename()); if (!f.open(QFile::ReadOnly)) { qWarning() << "Could not open resource" << resource->filename(); return false; } ba = f.readAll(); if (ba.size() == 0) { qWarning() << "Resource is empty" << resource->filename(); return false; } f.close(); buf.setBuffer(&ba); } else { qWarning() << "Could not find the resource " << resource->filename() << " or it isn't readable"; return false; } if (!buf.open(QBuffer::ReadOnly)) { qWarning() << "Could not open buffer"; return false; } Q_ASSERT(!store->hasFile(resType + "/" + resource->filename())); if (!store->open(resType + "/" + resource->filename())) { qWarning() << "Could not open file in store for resource"; return false; } bool res = (store->write(buf.data()) == buf.size()); store->close(); return res; } bool KoResourceBundle::save() { if (m_filename.isEmpty()) return false; setMetaData(KisResourceStorage::s_meta_dc_date, QDate::currentDate().toString("dd/MM/yyyy")); QDir bundleDir = KoResourcePaths::saveLocation("data", "bundles"); bundleDir.cdUp(); QScopedPointer store(KoStore::createStore(m_filename, KoStore::Write, "application/x-krita-resourcebundle", KoStore::Zip)); if (!store || store->bad()) return false; // Q_FOREACH (const QString &resType, m_manifest.types()) { // if (resType == ResourceType::Gradients) { // KoResourceServer* gradientServer = KoResourceServerProvider::instance()->gradientServer(); // Q_FOREACH (const KoResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { // KoResourceSP res = gradientServer->resourceByMD5(ref.md5sum); // if (!res) res = gradientServer->resourceByFilename(QFileInfo(ref.resourcePath).m_filename); // if (!saveResourceToStore(res, store.data(), ResourceType::Gradients)) { // if (res) { // qWarning() << "Could not save resource" << resType << res->name(); // } // else { // qWarning() << "could not find resource for" << QFileInfo(ref.resourcePath).m_filename; // } // } // } // } // else if (resType == ResourceType::Patterns) { // KoResourceServer* patternServer = KoResourceServerProvider::instance()->patternServer(); // Q_FOREACH (const KoResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { // KoResourceSP res = patternServer->resourceByMD5(ref.md5sum); // if (!res) res = patternServer->resourceByFilename(QFileInfo(ref.resourcePath).m_filename); // if (!saveResourceToStore(res, store.data(), ResourceType::Patterns)) { // if (res) { // qWarning() << "Could not save resource" << resType << res->name(); // } // else { // qWarning() << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); // } // } // } // } // else if (resType == ResourceType::Brushes) { // KoResourceServer* brushServer = KisBrushServerProvider::instance()->brushServer(); // Q_FOREACH (const KoResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { // KisBrushSP brush = brushServer->resourceByMD5(ref.md5sum); // if (!brush) brush = brushServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); // KoResourceSP res = brush.data(); // if (!saveResourceToStore(res, store.data(), ResourceType::Brushes)) { // if (res) { // qWarning() << "Could not save resource" << resType << res->name(); // } // else { // qWarning() << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); // } // } // } // } // else if (resType == ResourceType::Palettes) { // KoResourceServer* paletteServer = KoResourceServerProvider::instance()->paletteServer(); // Q_FOREACH (const KoResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { // KoResourceSP res = paletteServer->resourceByMD5(ref.md5sum); // if (!res) res = paletteServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); // if (!saveResourceToStore(res, store.data(), ResourceType::Palettes)) { // if (res) { // qWarning() << "Could not save resource" << resType << res->name(); // } // else { // qWarning() << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); // } // } // } // } // else if (resType == ResourceType::Workspaces) { // KoResourceServer< KisWorkspaceResource >* workspaceServer = KisResourceServerProvider::instance()->workspaceServer(); // Q_FOREACH (const KoResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { // KoResourceSP res = workspaceServer->resourceByMD5(ref.md5sum); // if (!res) res = workspaceServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); // if (!saveResourceToStore(res, store.data(), ResourceType::Workspaces)) { // if (res) { // qWarning() << "Could not save resource" << resType << res->name(); // } // else { // qWarning() << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); // } // } // } // } // else if (resType == ResourceType::PaintOpPresets) { // KisPaintOpPresetResourceServer* paintoppresetServer = KisResourceServerProvider::instance()->paintOpPresetServer(); // Q_FOREACH (const KoResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { // KisPaintOpPresetSP res = paintoppresetServer->resourceByMD5(ref.md5sum); // if (!res) res = paintoppresetServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); // if (!saveResourceToStore(res.data(), store.data(), ResourceType::PaintOpPresets)) { // if (res) { // qWarning() << "Could not save resource" << resType << res->name(); // } // else { // qWarning() << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); // } // } // } // } // } if (!m_thumbnail.isNull()) { QByteArray byteArray; QBuffer buffer(&byteArray); m_thumbnail.save(&buffer, "PNG"); if (!store->open("preview.png")) qWarning() << "Could not open preview.png"; if (store->write(byteArray) != buffer.size()) qWarning() << "Could not write preview.png"; store->close(); } saveManifest(store); saveMetadata(store); store->finalize(); return true; } bool KoResourceBundle::saveToDevice(QIODevice */*dev*/) const { return false; } void KoResourceBundle::setMetaData(const QString &key, const QString &value) { m_metadata.insert(key, value); } const QString KoResourceBundle::metaData(const QString &key, const QString &defaultValue) const { if (m_metadata.contains(key)) { return m_metadata[key]; } else { return defaultValue; } } void KoResourceBundle::addResource(QString fileType, QString filePath, QVector fileTagList, const QByteArray md5sum) { QStringList tags; Q_FOREACH(KisTagSP tag, fileTagList) { tags << tag->url(); } m_manifest.addResource(fileType, filePath, tags, md5sum); } QList KoResourceBundle::getTagsList() { return QList::fromSet(m_bundletags); } QStringList KoResourceBundle::resourceTypes() const { return m_manifest.types(); } void KoResourceBundle::setThumbnail(QString filename) { if (QFileInfo(filename).exists()) { m_thumbnail = QImage(filename); m_thumbnail = m_thumbnail.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation); } else { m_thumbnail = QImage(256, 256, QImage::Format_ARGB32); QPainter gc(&m_thumbnail); gc.fillRect(0, 0, 256, 256, Qt::red); gc.end(); } } void KoResourceBundle::writeMeta(const QString &metaTag, KoXmlWriter *writer) { if (m_metadata.contains(metaTag)) { writer->startElement(metaTag.toUtf8()); writer->addTextNode(m_metadata[metaTag].toUtf8()); writer->endElement(); } } void KoResourceBundle::writeUserDefinedMeta(const QString &metaTag, KoXmlWriter *writer) { if (m_metadata.contains(metaTag)) { writer->startElement("meta:meta-userdefined"); writer->addAttribute("meta:name", metaTag); writer->addAttribute("meta:value", m_metadata[metaTag]); writer->endElement(); } } bool KoResourceBundle::readMetaData(KoStore *resourceStore) { if (resourceStore->open("meta.xml")) { KoXmlDocument doc; if (!doc.setContent(resourceStore->device())) { qWarning() << "Could not parse meta.xml for" << m_filename; return false; } // First find the manifest:manifest node. KoXmlNode n = doc.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { if (!n.isElement()) { continue; } if (n.toElement().tagName() == "meta:meta") { break; } } if (n.isNull()) { qWarning() << "Could not find manifest node for bundle" << m_filename; return false; } const KoXmlElement metaElement = n.toElement(); for (n = metaElement.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.isElement()) { KoXmlElement e = n.toElement(); if (e.tagName() == "meta:meta-userdefined") { if (e.attribute("meta:name") == "tag") { m_bundletags << e.attribute("meta:value"); } else { m_metadata.insert(e.attribute("meta:name"), e.attribute("meta:value")); } } else { m_metadata.insert(e.tagName(), e.firstChild().toText().data()); } } } resourceStore->close(); return true; } return false; } void KoResourceBundle::saveMetadata(QScopedPointer &store) { QBuffer buf; store->open("meta.xml"); buf.open(QBuffer::WriteOnly); KoXmlWriter metaWriter(&buf); metaWriter.startDocument("office:document-meta"); metaWriter.startElement("meta:meta"); writeMeta(KisResourceStorage::s_meta_generator, &metaWriter); metaWriter.startElement(KisResourceStorage::s_meta_version.toUtf8()); metaWriter.addTextNode(m_bundleVersion.toUtf8()); metaWriter.endElement(); writeMeta(KisResourceStorage::s_meta_author, &metaWriter); writeMeta(KisResourceStorage::s_meta_title, &metaWriter); writeMeta(KisResourceStorage::s_meta_description, &metaWriter); writeMeta(KisResourceStorage::s_meta_initial_creator, &metaWriter); writeMeta(KisResourceStorage::s_meta_creator, &metaWriter); writeMeta(KisResourceStorage::s_meta_creation_date, &metaWriter); writeMeta(KisResourceStorage::s_meta_dc_date, &metaWriter); writeUserDefinedMeta("email", &metaWriter); writeUserDefinedMeta("license", &metaWriter); writeUserDefinedMeta("website", &metaWriter); Q_FOREACH (const QString &tag, m_bundletags) { metaWriter.startElement(KisResourceStorage::s_meta_user_defined.toUtf8()); metaWriter.addAttribute(KisResourceStorage::s_meta_name.toUtf8(), "tag"); metaWriter.addAttribute(KisResourceStorage::s_meta_value.toUtf8(), tag); metaWriter.endElement(); } metaWriter.endElement(); // meta:meta metaWriter.endDocument(); buf.close(); store->write(buf.data()); store->close(); } void KoResourceBundle::saveManifest(QScopedPointer &store) { store->open("META-INF/manifest.xml"); QBuffer buf; buf.open(QBuffer::WriteOnly); m_manifest.save(&buf); buf.close(); store->write(buf.data()); store->close(); } int KoResourceBundle::resourceCount() const { return m_manifest.files().count(); } KoResourceBundleManifest &KoResourceBundle::manifest() { return m_manifest; } KoResourceSP KoResourceBundle::resource(const QString &resourceType, const QString &filepath) { if (m_filename.isEmpty()) return 0; QScopedPointer resourceStore(KoStore::createStore(m_filename, KoStore::Read, "application/x-krita-resourcebundle", KoStore::Zip)); if (!resourceStore || resourceStore->bad()) { qWarning() << "Could not open store on bundle" << m_filename; return 0; } if (!resourceStore->open(filepath)) { qWarning() << "Could not open file in bundle" << filepath; } QString mime = KisMimeDatabase::mimeTypeForSuffix(filepath); KisResourceLoaderBase *loader = KisResourceLoaderRegistry::instance()->loader(resourceType, mime); if (!loader) { qWarning() << "Could not create loader for" << resourceType << filepath << mime; return 0; } KoResourceSP res = loader->load(filepath, *resourceStore->device(), KisGlobalResourcesInterface::instance()); resourceStore->close(); return res; } QImage KoResourceBundle::image() const { return m_thumbnail; } QString KoResourceBundle::filename() const { return m_filename; } diff --git a/libs/widgets/KoResourceServer.h b/libs/resources/KoResourceServer.h similarity index 94% rename from libs/widgets/KoResourceServer.h rename to libs/resources/KoResourceServer.h index 77dbac0be5..6d14428348 100644 --- a/libs/widgets/KoResourceServer.h +++ b/libs/resources/KoResourceServer.h @@ -1,371 +1,359 @@ /* This file is part of the KDE project Copyright (c) 1999 Matthias Elter Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Sven Langkamp Copyright (c) 2007 Jan Hambrecht Copyright (C) 2011 Srikanth Tiyyagura Copyright (c) 2013 Sascha Suelzer Copyright (c) 2003-2019 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KORESOURCESERVER_H #define KORESOURCESERVER_H #include #include #include #include -#include #include #include #include #include - +#include #include -#include + #include "KoResource.h" #include "KoResourceServerObserver.h" #include "KoResourcePaths.h" +#include "ksharedconfig.h" + #include #include #include #include #include -#include -#include - -#include "kritawidgets_export.h" -#include "WidgetsDebug.h" +#include class KoResource; /** - * KoResourceServer manages the resources of one type. It stores, - * loads and saves the resources. To keep track of changes the server - * can be observed with a KoResourceServerObserver + * KoResourceServer is a shim around KisResourceModel. It knows + * nothing by its own, and does nothing on its own. It can only + * be used in the gui thread. */ template class KoResourceServer { public: typedef KoResourceServerObserver ObserverType; KoResourceServer(const QString& type) : m_resourceModel(KisResourceModelProvider::resourceModel(type)) , m_tagModel(KisTagModelProvider::tagModel(type)) , m_type(type) { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } } virtual ~KoResourceServer() { Q_FOREACH (ObserverType* observer, m_observers) { observer->unsetResourceServer(); } } /// @return the active resource model KisResourceModel *resourceModel() const { QMutexLocker l(&m_mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } return m_resourceModel; } /// Return the first resource available QSharedPointer firstResource() const { QMutexLocker l(&m_mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } return m_resourceModel->resourceForIndex(m_resourceModel->index(0, 0)).dynamicCast(); } int resourceCount() const { QMutexLocker l(&m_mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); return m_resourceModel->rowCount(); } /// Adds an already loaded resource to the server bool addResource(QSharedPointer resource, bool save = true) { QMutexLocker l(&m_mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } if (!resource->valid()) { - warnWidgets << "Tried to add an invalid resource!"; + warnResource << "Tried to add an invalid resource!"; return false; } if (m_resourceModel->addResource(resource, save ? "" : "memory")) { notifyResourceAdded(resource); return true; } return false; } /// Remove a resource from Resource Server but not from a file bool removeResourceFromServer(QSharedPointer resource){ QMutexLocker l(&m_mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } if (m_resourceModel->removeResource(resource)) { notifyRemovingResource(resource); return true; } return false; } QList> resources() { QMutexLocker l(&m_mutex); qDebug() << "KoResourceServer::resources()" << m_type; KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } KIS_SAFE_ASSERT_RECOVER_NOOP(m_type != "paintoppresets"); QList> resourceList; for (int row = 0; row < m_resourceModel->rowCount(); ++row) { resourceList << m_resourceModel->resourceForIndex(m_resourceModel->index(row, 0)).dynamicCast(); } return resourceList; } /// Returns path where to save user defined and imported resources to QString saveLocation() { return KoResourcePaths::saveLocation(m_type.toLatin1()); } /** * Creates a new resource from a given file and adds them to the resource server * The base implementation does only load one resource per file, override to implement collections * @param filename file name of the resource file to be imported * @param fileCreation decides whether to create the file in the saveLocation() directory */ bool importResourceFile(const QString &filename) { QMutexLocker l(&m_mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } return m_resourceModel->importResourceFile(filename); } /// Removes the resource file from the resource server void removeResourceFile(const QString & filename) { QFileInfo fi(filename); QSharedPointer resource = resourceByFilename(fi.fileName()); if (!resource) { - warnWidgets << "Resource file do not exist "; + warnResource << "Resource file do not exist "; return; } removeResourceFromServer(resource); } /** * Addes an observer to the server * @param observer the observer to be added * @param notifyLoadedResources determines if the observer should be notified about the already loaded resources */ void addObserver(ObserverType* observer) { if (observer && !m_observers.contains(observer)) { m_observers.append(observer); } } /** * Removes an observer from the server * @param observer the observer to be removed */ void removeObserver(ObserverType* observer) { int index = m_observers.indexOf(observer); if (index < 0) { return; } m_observers.removeAt( index ); } QSharedPointer resourceByFilename(const QString& filename) const { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } QMutexLocker l(&m_mutex); qDebug() << "resourceByFilename" << filename; if (filename.isEmpty() || filename.isNull()) { return 0; } return m_resourceModel->resourceForFilename(filename).dynamicCast(); } QSharedPointer resourceByName(const QString& name) const { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } QMutexLocker l(&m_mutex); qDebug() << "resourceByName" << name; if (name.isEmpty() || name.isNull()) { return 0; } return m_resourceModel->resourceForName(name).dynamicCast(); } QSharedPointer resourceByMD5(const QByteArray& md5) const { KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } QMutexLocker l(&m_mutex); qDebug() << "resourceByMD5" << md5.toHex(); if (md5.isEmpty() || md5.isNull()) { return 0; } return m_resourceModel->resourceForMD5(md5).dynamicCast(); } /** * Call after changing the content of a resource; * Notifies the connected views. */ void updateResource(QSharedPointer resource) { QMutexLocker l(&m_mutex); KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread()); if (QThread::currentThread() != qApp->thread()) { Q_FOREACH(const QString &s, kisBacktrace().split('\n')) { qDebug() << s; } } - qDebug() << "updateResource" << resource; m_resourceModel->updateResource(resource); notifyResourceChanged(resource); } QVector assignedTagsList(KoResourceSP resource) const { if (resource.isNull()) { return QVector(); } return m_resourceModel->tagsForResource(resource->resourceId()); } - QVector resourcesForTag(KisTagSP tag) const - { - if (tag.isNull()) { - return QVector(); - } - return m_tagModel->resourcesForTag(tag->id()); - } - protected: void notifyResourceAdded(QSharedPointer resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->resourceAdded(resource); } } void notifyRemovingResource(QSharedPointer resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->removingResource(resource); } } void notifyResourceChanged(QSharedPointer resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->resourceChanged(resource); } } private: QList m_observers; KisResourceModel *m_resourceModel {0}; KisTagModel *m_tagModel {0}; QString m_type; mutable QMutex m_mutex; }; #endif // KORESOURCESERVER_H diff --git a/libs/resources/Mainpage.dox b/libs/resources/Mainpage.dox new file mode 100644 index 0000000000..658f33ce06 --- /dev/null +++ b/libs/resources/Mainpage.dox @@ -0,0 +1,33 @@ +/** + \mainpage Resources + + The Resources library provides cached access to resources. Resources are things + like brush presets, brush tips or gradients. The following components play an + important role in the library: + + - KisResourceModel: the public entry point to the library. This is a QAbstractItemModel + and can be used to represent resources in the user interface without loading the + actual resource until it is needed. KisResourceModel is available through KisResourceModelProvider. + + There is only one instance of a KisResourceModel for a given resource type, and KisResourceModel + can only be used from the gui thread. Accessing the data() method will in general not load the + actual resource. + + - KisResourceLocator: KisResourceLocator should not be used outside of the resources library. + Its task is to load the resources from the resource storages: resource storages are the + resource folder, bundles, ASL or ABR files that contain a collection of resources. Once a + resource is loaded, it's cached. Loading resources is handled through the KisResourceLoader + classes, while finding resources in the storages is handled through the KisStoragePlugin + implementations. + + All resources are captured in a QSharedPointer and should not be used naked. + + KisResourceLocator is also repsonsible for initializing the cache database. + + - KisResourceCacheDB: this class creates the sqlite database and handles synchronizing resources + with he contents of storages. + + */ + +// DOXYGEN_SET_PROJECT_NAME = KritaResources +// DOXYGEN_SET_IGNORE_PREFIX = Kis Ko K diff --git a/libs/resources/tests/TestStorageModel.h b/libs/resources/ResourceDebug.cpp similarity index 62% copy from libs/resources/tests/TestStorageModel.h copy to libs/resources/ResourceDebug.cpp index 95e03f8ae7..674155587c 100644 --- a/libs/resources/tests/TestStorageModel.h +++ b/libs/resources/ResourceDebug.cpp @@ -1,40 +1,27 @@ /* - * Copyright (c) 2018 boud + * Copyright (c) 2020 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 TESTRESOURCETYPEMODEL_H -#define TESTRESOURCETYPEMODEL_H -#include -class KisResourceLocator; +#include "ResourceDebug.h" -class TestStorageModel : public QObject +const QLoggingCategory &RESOURCE_LOG() \ { - Q_OBJECT -private Q_SLOTS: - void initTestCase(); - void testRowCount(); - void testSetActive(); - void cleanupTestCase(); + static const QLoggingCategory category("krita.lib.resource", QtInfoMsg); + return category; +} -private: - QString m_srcLocation; - QString m_dstLocation; - KisResourceLocator *m_locator; -}; - -#endif diff --git a/libs/resources/tests/TestStorageModel.h b/libs/resources/ResourceDebug.h similarity index 63% copy from libs/resources/tests/TestStorageModel.h copy to libs/resources/ResourceDebug.h index 95e03f8ae7..d5cf113b1d 100644 --- a/libs/resources/tests/TestStorageModel.h +++ b/libs/resources/ResourceDebug.h @@ -1,40 +1,31 @@ /* - * Copyright (c) 2018 boud + * Copyright (c) 2020 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 TESTRESOURCETYPEMODEL_H -#define TESTRESOURCETYPEMODEL_H +#ifndef RESOURCE_DEBUG_H_ +#define RESOURCE_DEBUG_H_ -#include -class KisResourceLocator; +#include +#include +#include -class TestStorageModel : public QObject -{ - Q_OBJECT -private Q_SLOTS: - void initTestCase(); - void testRowCount(); - void testSetActive(); - void cleanupTestCase(); +extern const KRITARESOURCES_EXPORT QLoggingCategory &RESOURCE_LOG(); -private: - - QString m_srcLocation; - QString m_dstLocation; - KisResourceLocator *m_locator; -}; +#define debugResource qCDebug(RESOURCE_LOG) +#define warnResource qCWarning(RESOURCE_LOG) +#define errorResource qCCritical(RESOURCE_LOG) #endif diff --git a/libs/resources/tests/DummyResource.h b/libs/resources/tests/DummyResource.h index 9d19e63190..64e36de488 100644 --- a/libs/resources/tests/DummyResource.h +++ b/libs/resources/tests/DummyResource.h @@ -1,121 +1,120 @@ /* * Copyright (c) 2018 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DUMMYRESOURCE_H #define DUMMYRESOURCE_H #include "KoResource.h" #include #include -#include -#include +#include +#include #include class DummyResource : public KoResource { public: DummyResource(const QString &f, const QString &resourceType = ResourceType::PaintOpPresets) : KoResource(f) , m_resourceType(resourceType) { QRandomGenerator64 qrg; QByteArray ba(1024, '0'); for (int i = 0; i < 1024 / 8; i+=8) { quint64 v = qrg.generate64(); ba[i] = v; } - KoHashGenerator *hashGenerator = KoHashGeneratorProvider::instance()->getGenerator("MD5"); - QByteArray hash = hashGenerator->generateHash(ba); + QByteArray hash = KoMD5Generator::generateHash(ba); setMD5(hash); setValid(true); } DummyResource(const DummyResource &rhs) : KoResource(rhs) { *this = rhs; } DummyResource &operator=(const DummyResource &rhs) { if (*this != rhs) { m_something = rhs.m_something; } return *this; } KoResourceSP clone() const override { return KoResourceSP(new DummyResource(*this)); } bool load(KisResourcesInterfaceSP resourcesInterface) override { Q_UNUSED(resourcesInterface); Q_ASSERT(false); setValid(true); return true; } bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override { Q_UNUSED(resourcesInterface); if (!dev->isOpen()) { dev->open(QIODevice::ReadOnly); } m_something = QString::fromUtf8(dev->readAll()); setValid(true); return true; } bool save() override { Q_ASSERT(false); return true; } bool saveToDevice(QIODevice *dev) const { if (!dev->isOpen()) { dev->open(QIODevice::WriteOnly); } dev->write(m_something.toUtf8()); return true; } void setSomething(const QString &something) { m_something = something; } QString something() const { return m_something; } QPair resourceType() const override { return QPair(m_resourceType, ""); } private: QString m_something; QString m_resourceType; }; #endif // DUMMYRESOURCE_H diff --git a/libs/resources/tests/TestBundleStorage.cpp b/libs/resources/tests/TestBundleStorage.cpp index b5c76177c9..6c863dd887 100644 --- a/libs/resources/tests/TestBundleStorage.cpp +++ b/libs/resources/tests/TestBundleStorage.cpp @@ -1,107 +1,105 @@ /* * Copyright (c) 2018 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TestBundleStorage.h" #include #include #include #include #include #include #include #include "DummyResource.h" void TestBundleStorage::initTestCase() { KisResourceLoaderRegistry *reg = KisResourceLoaderRegistry::instance(); reg->add(new KisResourceLoader(ResourceSubType::GbrBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/x-gimp-brush")); reg->add(new KisResourceLoader(ResourceSubType::GihBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/x-gimp-brush-animated")); reg->add(new KisResourceLoader(ResourceSubType::SvgBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceSubType::PngBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/png")); reg->add(new KisResourceLoader(ResourceType::PaintOpPresets, ResourceType::PaintOpPresets, i18n("Brush presets"), QStringList() << "application/x-krita-paintoppreset")); QList src = QImageReader::supportedMimeTypes(); QStringList allImageMimes; Q_FOREACH(const QByteArray ba, src) { allImageMimes << QString::fromUtf8(ba); } reg->add(new KisResourceLoader(ResourceType::Patterns, ResourceType::Patterns, i18n("Patterns"), allImageMimes)); } void TestBundleStorage::testMetaData() { KisBundleStorage storage(KRITA_SOURCE_DIR + QString("/krita/data/bundles/Krita_4_Default_Resources.bundle")); QVERIFY(storage.location() == KRITA_SOURCE_DIR + QString("/krita/data/bundles/Krita_4_Default_Resources.bundle")); - qDebug() << storage.metaData(KisResourceStorage::s_meta_generator); - QVERIFY(!storage.metaData(KisResourceStorage::s_meta_generator).isEmpty()); - QVERIFY(!storage.metaData(KisResourceStorage::s_meta_author).isEmpty()); - QVERIFY(!storage.metaData(KisResourceStorage::s_meta_description).isEmpty()); - QVERIFY(!storage.metaData(KisResourceStorage::s_meta_initial_creator).isEmpty()); - QVERIFY(!storage.metaData(KisResourceStorage::s_meta_dc_date).isEmpty()); - QVERIFY(!storage.metaData(KisResourceStorage::s_meta_version).isEmpty()); + QVERIFY(!storage.metaData(KisResourceStorage::s_meta_generator).isNull()); + QVERIFY(!storage.metaData(KisResourceStorage::s_meta_author).isNull()); + QVERIFY(!storage.metaData(KisResourceStorage::s_meta_description).isNull()); + QVERIFY(!storage.metaData(KisResourceStorage::s_meta_initial_creator).isNull()); + QVERIFY(!storage.metaData(KisResourceStorage::s_meta_dc_date).isNull()); + QVERIFY(!storage.metaData(KisResourceStorage::s_meta_version).isNull()); } void TestBundleStorage::testResourceIterator() { KisBundleStorage storage(KRITA_SOURCE_DIR + QString("/krita/data/bundles/Krita_4_Default_Resources.bundle")); QSharedPointer iter = storage.resources(ResourceType::Brushes); QVERIFY(iter->hasNext()); int count = 0; while (iter->hasNext()) { iter->next(); - qDebug() << iter->url(); KoResourceSP res = iter->resource(); QVERIFY(res); count++; } QVERIFY(count > 0); } void TestBundleStorage::testTagIterator() { KisBundleStorage storage(KRITA_SOURCE_DIR + QString("/krita/data/bundles/Krita_4_Default_Resources.bundle")); QSharedPointer iter = storage.tags(ResourceType::PaintOpPresets); QVERIFY(iter->hasNext()); int count = 0; while (iter->hasNext()) { //qDebug() << iter->url() << iter->name() << iter->tag(); iter->next(); count++; } QVERIFY(count > 0); } void TestBundleStorage::testResourceItem() { KisBundleStorage storage(KRITA_SOURCE_DIR + QString("/krita/data/bundles/Krita_4_Default_Resources.bundle")); KisResourceStorage::ResourceItem item = storage.resourceItem("paintoppresets/g)_Dry_Brushing.kpp"); QVERIFY(!item.url.isEmpty()); } void TestBundleStorage::testResource() { KisBundleStorage storage(KRITA_SOURCE_DIR + QString("/krita/data/bundles/Krita_4_Default_Resources.bundle")); KoResourceSP res = storage.resource("paintoppresets/g)_Dry_Brushing.kpp"); QVERIFY(res); QVERIFY(res->filename() == "paintoppresets/g)_Dry_Brushing.kpp"); } QTEST_MAIN(TestBundleStorage) diff --git a/libs/resources/tests/TestFolderStorage.cpp b/libs/resources/tests/TestFolderStorage.cpp index a4082ad10c..b6ee5c4100 100644 --- a/libs/resources/tests/TestFolderStorage.cpp +++ b/libs/resources/tests/TestFolderStorage.cpp @@ -1,131 +1,131 @@ /* * 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 "TestFolderStorage.h" #include #include #include #include #include #include #include #include #include #include #include #include "DummyResource.h" #include "ResourceTestHelper.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing installing resources" #endif #ifndef FILES_DEST_DIR #error "FILES_DEST_DIR not set. A directory where data will be written to for testing installing resources" #endif void TestFolderStorage::initTestCase() { ResourceTestHelper::initTestDb(); m_srcLocation = QString(FILES_DATA_DIR); QVERIFY2(QDir(m_srcLocation).exists(), m_srcLocation.toUtf8()); m_dstLocation = QString(FILES_DEST_DIR); ResourceTestHelper::cleanDstLocation(m_dstLocation); KConfigGroup cfg(KSharedConfig::openConfig(), ""); cfg.writeEntry(KisResourceLocator::resourceLocationKey, m_dstLocation); m_locator = KisResourceLocator::instance(); ResourceTestHelper::createDummyLoaderRegistry(); KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); m_locator->initialize(m_srcLocation); if (!m_locator->errorMessages().isEmpty()) qDebug() << m_locator->errorMessages(); } void TestFolderStorage ::testStorage() { KisFolderStorage folderStorage(m_dstLocation); KisResourceLoaderRegistry::instance()->add(ResourceType::Brushes, new KisResourceLoader("dummy", ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/x-gimp-brush")); QSharedPointer iter = folderStorage.resources(ResourceType::Brushes); QVERIFY(iter->hasNext()); int count = 0; while (iter->hasNext()) { iter->next(); qDebug() << iter->url() << iter->type() << iter->lastModified(); count++; } QVERIFY(count == 1); } void TestFolderStorage::testTagIterator() { KisFolderStorage folderStorage(m_dstLocation); QSharedPointer iter = folderStorage.tags(ResourceType::PaintOpPresets); QVERIFY(iter->hasNext()); int count = 0; while (iter->hasNext()) { iter->next(); qDebug() << iter->url() << iter->name() << iter->tag(); count++; } QVERIFY(count == 1); } void TestFolderStorage::testAddResource() { KoResourceSP resource(new DummyResource("anewresource.kpp")); resource->setValid(true); resource->setVersion(0); KisFolderStorage folderStorage(QString(FILES_DEST_DIR)); bool r = folderStorage.addResource("paintoppresets", resource); QVERIFY(r); QString fileName = resource->filename(); resource.dynamicCast()->setSomething("It's changed"); r = folderStorage.addResource("paintoppresets", resource); QVERIFY(r); QVERIFY(resource->filename() != fileName); QVERIFY(resource->version() == 1); QDir d(m_dstLocation + "/" + "paintoppresets"); - QVERIFY(d.entryList().contains("anewresource_0001.kpp")); + QVERIFY(d.entryList().contains("anewresource.0001.kpp")); } void TestFolderStorage::cleanupTestCase() { ResourceTestHelper::rmTestDb(); ResourceTestHelper::cleanDstLocation(m_dstLocation); } QTEST_MAIN(TestFolderStorage) diff --git a/libs/resources/tests/TestResourceLocator.cpp b/libs/resources/tests/TestResourceLocator.cpp index 096c27d970..3953201b39 100644 --- a/libs/resources/tests/TestResourceLocator.cpp +++ b/libs/resources/tests/TestResourceLocator.cpp @@ -1,180 +1,186 @@ /* * 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 "TestResourceLocator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing installing resources" #endif #ifndef FILES_DEST_DIR #error "FILES_DEST_DIR not set. A directory where data will be written to for testing installing resources" #endif void TestResourceLocator::initTestCase() { ResourceTestHelper::initTestDb(); m_srcLocation = QString(FILES_DATA_DIR); QVERIFY2(QDir(m_srcLocation).exists(), m_srcLocation.toUtf8()); m_dstLocation = QString(FILES_DEST_DIR); ResourceTestHelper::cleanDstLocation(m_dstLocation); KConfigGroup cfg(KSharedConfig::openConfig(), ""); cfg.writeEntry(KisResourceLocator::resourceLocationKey, m_dstLocation); m_locator = KisResourceLocator::instance(); ResourceTestHelper::createDummyLoaderRegistry(); } void TestResourceLocator::testLocatorInitalization() { KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); KisResourceLocator::LocatorError r = m_locator->initialize(m_srcLocation); if (!m_locator->errorMessages().isEmpty()) qDebug() << m_locator->errorMessages(); QVERIFY(r == KisResourceLocator::LocatorError::Ok); QVERIFY(QDir(m_dstLocation).exists()); Q_FOREACH(const QString &folder, KisResourceLoaderRegistry::instance()->resourceTypes()) { QDir dstDir(m_dstLocation + '/' + folder + '/'); QDir srcDir(m_srcLocation + '/' + folder + '/'); QVERIFY(dstDir.exists()); QVERIFY(dstDir.entryList(QDir::Files | QDir::NoDotAndDotDot) == srcDir.entryList(QDir::Files | QDir::NoDotAndDotDot)); } QFile f(m_dstLocation + '/' + "KRITA_RESOURCE_VERSION"); QVERIFY(f.exists()); f.open(QFile::ReadOnly); QVersionNumber version = QVersionNumber::fromString(QString::fromUtf8(f.readAll())); QVERIFY(version == QVersionNumber::fromString(KritaVersionWrapper::versionString())); } void TestResourceLocator::testStorageInitialization() { Q_FOREACH(KisResourceStorageSP storage, m_locator->storages()) { QVERIFY(KisResourceCacheDb::addStorage(storage, true)); } QSqlQuery query; bool r = query.exec("SELECT COUNT(*) FROM storages"); QVERIFY(r); QVERIFY(query.lastError() == QSqlError()); query.first(); QCOMPARE(query.value(0).toInt(), m_locator->storages().count()); } void TestResourceLocator::testLocatorSynchronization() { QVERIFY(m_locator->synchronizeDb()); { QSqlQuery query; bool r = query.exec("SELECT COUNT(*) FROM resources"); QVERIFY(r); QVERIFY(query.lastError() == QSqlError()); query.first(); QCOMPARE(query.value(0).toInt(), 7); } { QSqlQuery query; bool r = query.exec("SELECT COUNT(*) FROM tags"); QVERIFY(r); QVERIFY(query.lastError() == QSqlError()); query.first(); QCOMPARE(query.value(0).toInt(), 1); } } void TestResourceLocator::testResourceLocationBase() { QCOMPARE(m_locator->resourceLocationBase(), QString(FILES_DEST_DIR)); } void TestResourceLocator::testResource() { KoResourceSP res = m_locator->resource("", ResourceType::PaintOpPresets, "test0.kpp"); QVERIFY(res); } void TestResourceLocator::testResourceForId() { KoResourceSP res = m_locator->resource("", ResourceType::PaintOpPresets, "test0.kpp"); int resourceId = KisResourceCacheDb::resourceIdForResource("test0.kpp", ResourceType::PaintOpPresets, ""); QVERIFY(resourceId > -1); KoResourceSP res2 = m_locator->resourceForId(resourceId); QCOMPARE(res, res2); } +void TestResourceLocator::testStorageContainsResourceByFile() +{ + QVERIFY(m_locator->storageContainsResourceByFile("", "paintoppresets", "test0.kpp") > 0); + QVERIFY(m_locator->storageContainsResourceByFile("", "paintoppresets", "XSLKDJSADLKSAJDA") == 0); +} + void TestResourceLocator::testDocumentStorage() { const QString &documentName("document"); KisResourceModel *model = KisResourceModelProvider::resourceModel(ResourceType::PaintOpPresets); int rowcount = model->rowCount(); KisResourceStorageSP documentStorage = QSharedPointer::create(documentName); KoResourceSP resource(new DummyResource("test")); documentStorage->addResource(resource); m_locator->addStorage(documentName, documentStorage); QVERIFY(model->rowCount() > rowcount); QVERIFY(m_locator->hasStorage(documentName)); m_locator->removeStorage(documentName); QVERIFY(!m_locator->hasStorage(documentName)); QVERIFY(model->rowCount() == rowcount); } void TestResourceLocator::cleanupTestCase() { - ResourceTestHelper::rmTestDb(); + //ResourceTestHelper::rmTestDb(); ResourceTestHelper::cleanDstLocation(m_dstLocation); } QTEST_MAIN(TestResourceLocator) diff --git a/libs/resources/tests/TestResourceLocator.h b/libs/resources/tests/TestResourceLocator.h index cd74b9ee61..3720888c56 100644 --- a/libs/resources/tests/TestResourceLocator.h +++ b/libs/resources/tests/TestResourceLocator.h @@ -1,53 +1,53 @@ /* * 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. */ #ifndef TESTRESOURCELOCATOR_H #define TESTRESOURCELOCATOR_H #include class KisResourceLocator; class TestResourceLocator : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testLocatorInitalization(); void testStorageInitialization(); void testLocatorSynchronization(); void testResourceLocationBase(); void testResource(); void testResourceForId(); - + void testStorageContainsResourceByFile(); void testDocumentStorage(); void cleanupTestCase(); private: QString m_srcLocation; QString m_dstLocation; KisResourceLocator *m_locator; }; #endif diff --git a/libs/resources/tests/TestResourceModel.cpp b/libs/resources/tests/TestResourceModel.cpp index 203a3ddebe..7e51cd25f8 100644 --- a/libs/resources/tests/TestResourceModel.cpp +++ b/libs/resources/tests/TestResourceModel.cpp @@ -1,277 +1,307 @@ /* * Copyright (c) 2018 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TestResourceModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing installing resources" #endif #ifndef FILES_DEST_DIR #error "FILES_DEST_DIR not set. A directory where data will be written to for testing installing resources" #endif void TestResourceModel::initTestCase() { ResourceTestHelper::initTestDb(); ResourceTestHelper::createDummyLoaderRegistry(); m_srcLocation = QString(FILES_DATA_DIR); QVERIFY2(QDir(m_srcLocation).exists(), m_srcLocation.toUtf8()); m_dstLocation = QString(FILES_DEST_DIR); ResourceTestHelper::cleanDstLocation(m_dstLocation); KConfigGroup cfg(KSharedConfig::openConfig(), ""); cfg.writeEntry(KisResourceLocator::resourceLocationKey, m_dstLocation); m_locator = KisResourceLocator::instance(); if (!KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) { qWarning() << "Could not initialize KisResourceCacheDb on" << QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); } QVERIFY(KisResourceCacheDb::isValid()); KisResourceLocator::LocatorError r = m_locator->initialize(m_srcLocation); - if (!m_locator->errorMessages().isEmpty()) qDebug() << m_locator->errorMessages(); + if (!m_locator->errorMessages().isEmpty()) { + qDebug() << m_locator->errorMessages(); + } QVERIFY(r == KisResourceLocator::LocatorError::Ok); QVERIFY(QDir(m_dstLocation).exists()); } void TestResourceModel::testRowCount() { QSqlQuery q; QVERIFY(q.prepare("SELECT count(*)\n" "FROM resources\n" ", resource_types\n" "WHERE resources.resource_type_id = resource_types.id\n" "AND resource_types.name = :resource_type")); q.bindValue(":resource_type", m_resourceType); QVERIFY(q.exec()); q.first(); int rowCount = q.value(0).toInt(); QVERIFY(rowCount == 3); KisResourceModel resourceModel(m_resourceType); QCOMPARE(resourceModel.rowCount(), rowCount); } void TestResourceModel::testData() { KisResourceModel resourceModel(m_resourceType); QStringList resourceNames; for (int i = 0; i < resourceModel.rowCount(); ++i) { QVariant v = resourceModel.data(resourceModel.index(i, KisResourceModel::Name), Qt::DisplayRole); resourceNames << v.toString(); } QVERIFY(resourceNames.contains("test0.kpp")); QVERIFY(resourceNames.contains("test1.kpp")); QVERIFY(resourceNames.contains("test2.kpp")); } void TestResourceModel::testResourceForIndex() { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(0, 0)); QVERIFY(resource); } void TestResourceModel::testIndexFromResource() { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(1, 0)); QModelIndex idx = resourceModel.indexFromResource(resource); QVERIFY(idx.row() == 1); QVERIFY(idx.column() == 0); } void TestResourceModel::testRemoveResourceByIndex() { KisResourceModel resourceModel(m_resourceType); int resourceCount = resourceModel.rowCount(); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(1, 0)); bool r = resourceModel.removeResource(resourceModel.index(1, 0)); QVERIFY(r); QCOMPARE(resourceCount - 1, resourceModel.rowCount()); QVERIFY(!resourceModel.indexFromResource(resource).isValid()); } void TestResourceModel::testRemoveResource() { KisResourceModel resourceModel(m_resourceType); int resourceCount = resourceModel.rowCount(); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(1, 0)); QVERIFY(resource); bool r = resourceModel.removeResource(resource); QVERIFY(r); QCOMPARE(resourceCount - 1, resourceModel.rowCount()); QVERIFY(!resourceModel.indexFromResource(resource).isValid()); } void TestResourceModel::testImportResourceFile() { KisResourceModel resourceModel(m_resourceType); QTemporaryFile f(QDir::tempPath() + "/testresourcemodel-testimportresourcefile-XXXXXX.kpp"); f.open(); f.write("0"); f.close(); int resourceCount = resourceModel.rowCount(); bool r = resourceModel.importResourceFile(f.fileName()); QVERIFY(r); QCOMPARE(resourceCount + 1, resourceModel.rowCount()); } void TestResourceModel::testAddResource() { KisResourceModel resourceModel(m_resourceType); int resourceCount = resourceModel.rowCount(); KoResourceSP resource(new DummyResource("dummy.kpp")); resource->setValid(true); bool r = resourceModel.addResource(resource); QVERIFY(r); QCOMPARE(resourceCount + 1, resourceModel.rowCount()); } void TestResourceModel::testAddTemporaryResource() { KisResourceModel resourceModel(m_resourceType); int resourceCount = resourceModel.rowCount(); KoResourceSP resource(new DummyResource("dummy.kpp")); resource->setValid(true); bool r = resourceModel.addResource(resource, "memory"); QVERIFY(r); QCOMPARE(resourceCount + 1, resourceModel.rowCount()); } void TestResourceModel::testUpdateResource() { int resourceId; { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(0, 0)); QVERIFY(resource); QVERIFY(resource.dynamicCast()->something().isEmpty()); resource.dynamicCast()->setSomething("It's changed"); resourceId = resource->resourceId(); bool r = resourceModel.updateResource(resource); QVERIFY(r); } { // Check the resource itself KisResourceLocator::instance()->purge(); KoResourceSP resource = KisResourceLocator::instance()->resourceForId(resourceId); QVERIFY(resource); QVERIFY(!resource.dynamicCast()->something().isEmpty()); QVERIFY(resource->resourceId() == resourceId); // Check the versions in the database QSqlQuery q; QVERIFY(q.prepare("SELECT count(*)\n" "FROM versioned_resources\n" "WHERE resource_id = :resource_id\n")); q.bindValue(":resource_id", resourceId); QVERIFY(q.exec()); q.first(); int rowCount = q.value(0).toInt(); QCOMPARE(rowCount, 2); } } void TestResourceModel::testResourceForId() { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(0, 0)); QVERIFY(!resource.isNull()); KoResourceSP resource2 = resourceModel.resourceForId(resource->resourceId()); QVERIFY(!resource2.isNull()); QCOMPARE(resource, resource2); } void TestResourceModel::testResourceForName() { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(0, 0)); QVERIFY(!resource.isNull()); KoResourceSP resource2 = resourceModel.resourceForName(resource->name()); QVERIFY(!resource2.isNull()); QCOMPARE(resource, resource2); } void TestResourceModel::testResourceForFileName() { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(0, 0)); QVERIFY(!resource.isNull()); KoResourceSP resource2 = resourceModel.resourceForFilename(resource->filename()); QVERIFY(!resource2.isNull()); QCOMPARE(resource, resource2); } void TestResourceModel::testResourceForMD5() { KisResourceModel resourceModel(m_resourceType); KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(0, 0)); QVERIFY(!resource.isNull()); KoResourceSP resource2 = resourceModel.resourceForMD5(resource->md5()); QVERIFY(!resource2.isNull()); QCOMPARE(resource->md5(), resource2->md5()); } +void TestResourceModel::testRenameResource() +{ + KisResourceModel resourceModel(m_resourceType); + + KoResourceSP resource = resourceModel.resourceForIndex(resourceModel.index(1, 0)); + QVERIFY(!resource.isNull()); + const QString name = resource->name(); + bool r = resourceModel.renameResource(resource, "A New Name"); + QVERIFY(r); + QSqlQuery q; + if (!q.prepare("SELECT name\n" + "FROM resources\n" + "WHERE id = :resource_id\n")) { + qWarning() << "Could not prepare testRenameResource Query" << q.lastError(); + } + + q.bindValue(":resource_id", resource->resourceId()); + + if (!q.exec()) { + qWarning() << "Could not execute testRenameResource Query" << q.lastError(); + } + + q.first(); + QString newName = q.value(0).toString(); + QVERIFY(name != newName); + QCOMPARE("A New Name", newName); +} + void TestResourceModel::cleanupTestCase() { ResourceTestHelper::rmTestDb(); ResourceTestHelper::cleanDstLocation(m_dstLocation); } QTEST_MAIN(TestResourceModel) diff --git a/libs/resources/tests/TestResourceModel.h b/libs/resources/tests/TestResourceModel.h index c047b5b8df..b899fab411 100644 --- a/libs/resources/tests/TestResourceModel.h +++ b/libs/resources/tests/TestResourceModel.h @@ -1,57 +1,58 @@ /* * Copyright (c) 2018 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TESTRESOURCEMODEL_H #define TESTRESOURCEMODEL_H #include #include #include "KisResourceTypes.h" class KisResourceLocator; class TestResourceModel : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testRowCount(); void testData(); void testResourceForIndex(); void testIndexFromResource(); void testRemoveResourceByIndex(); void testRemoveResource(); void testImportResourceFile(); void testAddResource(); void testAddTemporaryResource(); void testUpdateResource(); void testResourceForId(); void testResourceForName(); void testResourceForFileName(); void testResourceForMD5(); + void testRenameResource(); void cleanupTestCase(); private: QString m_srcLocation; QString m_dstLocation; KisResourceLocator *m_locator; const QString m_resourceType = ResourceType::PaintOpPresets; }; #endif diff --git a/libs/resources/tests/TestStorageModel.cpp b/libs/resources/tests/TestStorageModel.cpp index da5b670008..da12267ea3 100644 --- a/libs/resources/tests/TestStorageModel.cpp +++ b/libs/resources/tests/TestStorageModel.cpp @@ -1,122 +1,155 @@ /* * Copyright (c) 2018 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TestStorageModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing installing resources" #endif #ifndef FILES_DEST_DIR #error "FILES_DEST_DIR not set. A directory where data will be written to for testing installing resources" #endif void TestStorageModel::initTestCase() { ResourceTestHelper::initTestDb(); ResourceTestHelper::createDummyLoaderRegistry(); m_srcLocation = QString(FILES_DATA_DIR); QVERIFY2(QDir(m_srcLocation).exists(), m_srcLocation.toUtf8()); m_dstLocation = QString(FILES_DEST_DIR); ResourceTestHelper::cleanDstLocation(m_dstLocation); KConfigGroup cfg(KSharedConfig::openConfig(), ""); cfg.writeEntry(KisResourceLocator::resourceLocationKey, m_dstLocation); m_locator = KisResourceLocator::instance(); if (!KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) { qDebug() << "Could not initialize KisResourceCacheDb on" << QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); } QVERIFY(KisResourceCacheDb::isValid()); KisResourceLocator::LocatorError r = m_locator->initialize(m_srcLocation); if (!m_locator->errorMessages().isEmpty()) qDebug() << m_locator->errorMessages(); QVERIFY(r == KisResourceLocator::LocatorError::Ok); QVERIFY(QDir(m_dstLocation).exists()); } void TestStorageModel::testRowCount() { QSqlQuery q; QVERIFY(q.prepare("SELECT count(*)\n" "FROM storages")); QVERIFY(q.exec()); q.first(); int rowCount = q.value(0).toInt(); KisStorageModel storageModel; QCOMPARE(storageModel.rowCount(), rowCount); } void TestStorageModel::testSetActive() { KisStorageModel storageModel; for (int i = 0; i < storageModel.rowCount(); ++i) { QModelIndex idx = storageModel.index(i, 0); storageModel.setData(idx, QVariant(true), Qt::CheckStateRole); QVERIFY(idx.data(Qt::UserRole + KisStorageModel::Active).toBool() == true); storageModel.setData(idx, QVariant(false), Qt::CheckStateRole); idx = storageModel.index(i, 0); QVERIFY(idx.data(Qt::UserRole + KisStorageModel::Active).toBool() == false); } } void TestStorageModel::cleanupTestCase() { ResourceTestHelper::rmTestDb(); ResourceTestHelper::cleanDstLocation(m_dstLocation); } +void TestStorageModel::testMetaData() +{ + KisStorageModel storageModel; + int rowCount = storageModel.rowCount(); + + KisResourceStorageSP storage {new KisResourceStorage("My Named Memory Storage")}; + KisResourceLocator::instance()->addStorage("My Named Memory Storage", storage); + storage->setMetaData(KisResourceStorage::s_meta_name, "My Named Memory Storage"); + storageModel.resetQuery(); + + QVERIFY(storageModel.rowCount() > rowCount); + + QModelIndex idx; + for (int row = 0; row < storageModel.rowCount(); ++row) { + idx = storageModel.index(row, 7); + KisResourceStorageSP st = storageModel.storageForIndex(idx); + QVERIFY(st); + if (st == storage) { + break; + } + } + + QVERIFY(idx.isValid()); + + QString displayName = storageModel.data(idx, Qt::DisplayRole).toString(); + QCOMPARE("My Named Memory Storage", displayName); + + idx = storageModel.index(idx.row(), 0); + QMap metadata = storageModel.data(idx, Qt::UserRole + KisStorageModel::MetaData).toMap(); + QVERIFY(metadata.contains(KisResourceStorage::s_meta_name)); + QVERIFY(metadata[KisResourceStorage::s_meta_name] == "My Named Memory Storage"); +} + QTEST_MAIN(TestStorageModel) diff --git a/libs/resources/tests/TestStorageModel.h b/libs/resources/tests/TestStorageModel.h index 95e03f8ae7..65ec9ca5b2 100644 --- a/libs/resources/tests/TestStorageModel.h +++ b/libs/resources/tests/TestStorageModel.h @@ -1,40 +1,41 @@ /* * Copyright (c) 2018 boud * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TESTRESOURCETYPEMODEL_H #define TESTRESOURCETYPEMODEL_H #include class KisResourceLocator; class TestStorageModel : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testRowCount(); void testSetActive(); void cleanupTestCase(); + void testMetaData(); private: QString m_srcLocation; QString m_dstLocation; KisResourceLocator *m_locator; }; #endif diff --git a/libs/resourcewidgets/KisIconToolTip.cpp b/libs/resourcewidgets/KisIconToolTip.cpp index a567782e6d..f3d319ee24 100644 --- a/libs/resourcewidgets/KisIconToolTip.cpp +++ b/libs/resourcewidgets/KisIconToolTip.cpp @@ -1,76 +1,76 @@ /* This file is part of the KDE project * Copyright (c) 1999 Carsten Pfeiffer (pfeiffer@kde.org) * Copyright (c) 2002 Igor Jansen (rm@kde.org) * Copyright (c) 2018 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 "KisIconToolTip.h" #include #include #include #include #include KisIconToolTip::KisIconToolTip() { } KisIconToolTip::~KisIconToolTip() { } QTextDocument *KisIconToolTip::createDocument(const QModelIndex &index) { QTextDocument *doc = new QTextDocument(this); QImage thumb = index.data(Qt::DecorationRole).value(); if (thumb.isNull()) { - thumb = index.data(Qt::UserRole + KisResourceModel::Image).value(); + thumb = index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); } doc->addResource(QTextDocument::ImageResource, QUrl("data:thumbnail"), thumb); QString name = index.data(Qt::DisplayRole).toString(); QString presetDisplayName = index.data(Qt::UserRole + KisResourceModel::Name).toString().replace("_", " "); //This is to ensure that the other uses of this class don't get an empty string, while resource management should get a nice string. if (!presetDisplayName.isEmpty()) { name = presetDisplayName; } QString tags; QString tagsData = index.data(Qt::UserRole + KisResourceModel::Tags).toStringList().join(", "); if (tagsData.length() > 0) { const QString list = QString("
    %1
").arg(tagsData); tags = QString("

%1:%2

").arg(i18n("Tags"), list); } const QString image = QString("
"); const QString body = QString("

%1

%2%3").arg(name, image, tags); const QString html = QString("%1").arg(body); doc->setHtml(html); const int margin = 16; doc->setTextWidth(qMin(doc->size().width() + 2 * margin, qreal(500.0))); doc->setDocumentMargin(margin); doc->setUseDesignMetrics(true); return doc; } diff --git a/libs/resourcewidgets/KisResourceItemChooser.cpp b/libs/resourcewidgets/KisResourceItemChooser.cpp index eaf9e79c2d..7c7ec5920a 100644 --- a/libs/resourcewidgets/KisResourceItemChooser.cpp +++ b/libs/resourcewidgets/KisResourceItemChooser.cpp @@ -1,548 +1,548 @@ /* This file is part of the KDE project Copyright (c) 2002 Patrick Julien Copyright (c) 2007 Jan Hambrecht Copyright (c) 2007 Sven Langkamp Copyright (C) 2011 Srikanth Tiyyagura Copyright (c) 2011 José Luis Vergara Copyright (c) 2013 Sascha Suelzer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisResourceItemChooser.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 "KisResourceItemListView.h" #include "KisResourceItemDelegate.h" #include "KisTagFilterWidget.h" #include "KisTagChooserWidget.h" #include "KisResourceItemChooserSync.h" #include "KisResourceTaggingManager.h" #include "KisTagModelProvider.h" #include "KisStorageChooserWidget.h" #include "kis_assert.h" class Q_DECL_HIDDEN KisResourceItemChooser::Private { public: Private(QString _resourceType) : resourceType(_resourceType) {} QString resourceType; KisResourceModel *resourceModel {0}; KisTagFilterResourceProxyModel *tagFilterProxyModel {0}; QSortFilterProxyModel *extraFilterModel {0}; KisResourceTaggingManager *tagManager {0}; KisResourceItemListView *view {0}; QButtonGroup *buttonGroup {0}; QToolButton *viewModeButton {0}; KisStorageChooserWidget *storagePopupButton {0}; QScrollArea *previewScroller {0}; QLabel *previewLabel {0}; QSplitter *splitter {0}; QGridLayout *buttonLayout {0}; QPushButton *importButton {0}; QPushButton *deleteButton {0}; bool usePreview {false}; bool tiledPreview {false}; bool grayscalePreview {false}; bool synced {false}; bool updatesBlocked {false}; QModelIndex savedResourceWhileReset; // Indexes on the proxyModel, not the source resource model QList customButtons; }; KisResourceItemChooser::KisResourceItemChooser(const QString &resourceType, bool usePreview, QWidget *parent, QSortFilterProxyModel *extraFilterProxy) : QWidget(parent) , d(new Private(resourceType)) { d->splitter = new QSplitter(this); d->resourceModel = KisResourceModelProvider::resourceModel(resourceType); d->tagFilterProxyModel = new KisTagFilterResourceProxyModel(KisTagModelProvider::tagModel(resourceType), this); d->extraFilterModel = extraFilterProxy; if (d->extraFilterModel) { d->extraFilterModel->setParent(this); d->extraFilterModel->setSourceModel(d->resourceModel); d->tagFilterProxyModel->setSourceModel(d->extraFilterModel); } else { d->tagFilterProxyModel->setSourceModel(d->resourceModel); } connect(d->resourceModel, SIGNAL(beforeResourcesLayoutReset(QModelIndex)), SLOT(slotBeforeResourcesLayoutReset(QModelIndex))); connect(d->resourceModel, SIGNAL(afterResourcesLayoutReset()), SLOT(slotAfterResourcesLayoutReset())); d->view = new KisResourceItemListView(this); d->view->setObjectName("ResourceItemview"); d->view->setModel(d->tagFilterProxyModel); d->view->setItemDelegate(new KisResourceItemDelegate(this)); d->view->setSelectionMode(QAbstractItemView::SingleSelection); d->view->viewport()->installEventFilter(this); connect(d->view, SIGNAL(currentResourceChanged(QModelIndex)), this, SLOT(activated(QModelIndex))); connect(d->view, SIGNAL(currentResourceClicked(QModelIndex)), this, SLOT(clicked(QModelIndex))); connect(d->view, SIGNAL(contextMenuRequested(QPoint)), this, SLOT(contextMenuRequested(QPoint))); connect(d->view, SIGNAL(sigSizeChanged()), this, SLOT(updateView())); d->splitter->addWidget(d->view); d->splitter->setStretchFactor(0, 2); d->usePreview = usePreview; if (d->usePreview) { d->previewScroller = new QScrollArea(this); d->previewScroller->setWidgetResizable(true); d->previewScroller->setBackgroundRole(QPalette::Dark); d->previewScroller->setVisible(true); d->previewScroller->setAlignment(Qt::AlignCenter); d->previewLabel = new QLabel(this); d->previewScroller->setWidget(d->previewLabel); d->splitter->addWidget(d->previewScroller); if (d->splitter->count() == 2) { d->splitter->setSizes(QList() << 280 << 160); } QScroller* scroller = KisKineticScroller::createPreconfiguredScroller(d->previewScroller); if (scroller) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } } d->splitter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); connect(d->splitter, SIGNAL(splitterMoved(int,int)), SIGNAL(splitterMoved())); d->buttonGroup = new QButtonGroup(this); d->buttonGroup->setExclusive(false); QGridLayout *layout = new QGridLayout(this); d->buttonLayout = new QGridLayout(); d->importButton = new QPushButton(this); d->importButton->setToolTip(i18nc("@info:tooltip", "Import resource")); d->importButton->setEnabled(true); d->buttonGroup->addButton(d->importButton, Button_Import); d->buttonLayout->addWidget(d->importButton, 0, 0); d->deleteButton = new QPushButton(this); d->deleteButton->setToolTip(i18nc("@info:tooltip", "Delete resource")); d->deleteButton->setEnabled(false); d->buttonGroup->addButton(d->deleteButton, Button_Remove); d->buttonLayout->addWidget(d->deleteButton, 0, 1); connect(d->buttonGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotButtonClicked(int))); d->buttonLayout->setColumnStretch(0, 1); d->buttonLayout->setColumnStretch(1, 1); d->buttonLayout->setColumnStretch(2, 2); d->buttonLayout->setSpacing(0); d->buttonLayout->setMargin(0); d->viewModeButton = new QToolButton(this); d->viewModeButton->setPopupMode(QToolButton::InstantPopup); d->viewModeButton->setVisible(false); d->tagManager = new KisResourceTaggingManager(resourceType, d->tagFilterProxyModel, this); d->storagePopupButton = new KisStorageChooserWidget(this); layout->addWidget(d->tagManager->tagChooserWidget(), 0, 0); layout->addWidget(d->viewModeButton, 0, 1); layout->addWidget(d->storagePopupButton, 0, 2); layout->addWidget(d->splitter, 1, 0, 1, 3); layout->addWidget(d->tagManager->tagFilterWidget(), 2, 0, 1, 3); layout->addLayout(d->buttonLayout, 3, 0, 1, 3); layout->setMargin(0); layout->setSpacing(0); updateView(); updateButtonState(); showTaggingBar(false); activated(d->view->model()->index(0, 0)); } KisResourceItemChooser::~KisResourceItemChooser() { disconnect(); delete d; } void KisResourceItemChooser::slotButtonClicked(int button) { if (button == Button_Import) { QStringList mimeTypes = KisResourceLoaderRegistry::instance()->mimeTypes(d->resourceType); KoFileDialog dialog(0, KoFileDialog::OpenFiles, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); Q_FOREACH(const QString &filename, dialog.filenames()) { if (QFileInfo(filename).exists() && QFileInfo(filename).isReadable()) { d->tagFilterProxyModel->importResourceFile(filename); } } } else if (button == Button_Remove) { QModelIndex index = d->view->currentIndex(); if (index.isValid()) { d->tagFilterProxyModel->removeResource(index); } int row = index.row(); int rowMin = --row; row = qBound(0, rowMin, row); setCurrentItem(row); activated(d->tagFilterProxyModel->index(row, index.column())); } updateButtonState(); } void KisResourceItemChooser::showButtons(bool show) { foreach (QAbstractButton * button, d->buttonGroup->buttons()) { show ? button->show() : button->hide(); } Q_FOREACH (QAbstractButton *button, d->customButtons) { show ? button->show() : button->hide(); } } void KisResourceItemChooser::addCustomButton(QAbstractButton *button, int cell) { d->buttonLayout->addWidget(button, 0, cell); d->buttonLayout->setColumnStretch(2, 1); d->buttonLayout->setColumnStretch(3, 1); } void KisResourceItemChooser::showTaggingBar(bool show) { d->tagManager->showTaggingBar(show); } int KisResourceItemChooser::rowCount() const { return d->view->model()->rowCount(); } void KisResourceItemChooser::setRowHeight(int rowHeight) { d->view->setItemSize(QSize(d->view->gridSize().width(), rowHeight)); } void KisResourceItemChooser::setColumnWidth(int columnWidth) { d->view->setItemSize(QSize(columnWidth, d->view->gridSize().height())); } void KisResourceItemChooser::setItemDelegate(QAbstractItemDelegate *delegate) { d->view->setItemDelegate(delegate); } KoResourceSP KisResourceItemChooser::currentResource() const { QModelIndex index = d->view->currentIndex(); if (index.isValid()) { return resourceFromModelIndex(index); } return 0; } void KisResourceItemChooser::setCurrentResource(KoResourceSP resource) { // don't update if the change came from the same chooser if (d->updatesBlocked) { return; } QModelIndex index = d->resourceModel->indexFromResource(resource); d->view->setCurrentIndex(index); updatePreview(index); } void KisResourceItemChooser::slotBeforeResourcesLayoutReset(QModelIndex activateAfterReset) { QModelIndex proxyIndex = d->tagFilterProxyModel->mapFromSource(d->tagFilterProxyModel->mapFromSource(activateAfterReset)); d->savedResourceWhileReset = proxyIndex.isValid() ? proxyIndex : d->view->currentIndex(); } void KisResourceItemChooser::slotAfterResourcesLayoutReset() { if (d->savedResourceWhileReset.isValid()) { this->blockSignals(true); setCurrentItem(d->savedResourceWhileReset.row()); this->blockSignals(false); } d->savedResourceWhileReset = QModelIndex(); } void KisResourceItemChooser::setPreviewOrientation(Qt::Orientation orientation) { d->splitter->setOrientation(orientation); } void KisResourceItemChooser::setPreviewTiled(bool tiled) { d->tiledPreview = tiled; } void KisResourceItemChooser::setGrayscalePreview(bool grayscale) { d->grayscalePreview = grayscale; } void KisResourceItemChooser::setCurrentItem(int row) { QModelIndex index = d->view->model()->index(row, 0); if (!index.isValid()) return; d->view->setCurrentIndex(index); if (index.isValid()) { updatePreview(index); } } void KisResourceItemChooser::activated(const QModelIndex &index) { if (!index.isValid()) return; KoResourceSP resource = 0; if (index.isValid()) { resource = resourceFromModelIndex(index); } if (resource && resource->valid()) { d->updatesBlocked = true; emit resourceSelected(resource); d->updatesBlocked = false; updatePreview(index); updateButtonState(); } } void KisResourceItemChooser::clicked(const QModelIndex &index) { Q_UNUSED(index); KoResourceSP resource = currentResource(); if (resource) { emit resourceClicked(resource); } } void KisResourceItemChooser::updateButtonState() { QAbstractButton *removeButton = d->buttonGroup->button(Button_Remove); if (! removeButton) return; KoResourceSP resource = currentResource(); if (resource) { removeButton->setEnabled(!resource->permanent()); return; } removeButton->setEnabled(false); } void KisResourceItemChooser::updatePreview(const QModelIndex &idx) { if (!d->usePreview) return; if (!idx.isValid()) { d->previewLabel->setPixmap(QPixmap()); return; } - QImage image = idx.data(Qt::UserRole + KisResourceModel::Image).value(); + QImage image = idx.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); if (image.format() != QImage::Format_RGB32 && image.format() != QImage::Format_ARGB32 && image.format() != QImage::Format_ARGB32_Premultiplied) { image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); } if (d->tiledPreview) { int width = d->previewScroller->width() * 4; int height = d->previewScroller->height() * 4; QImage img(width, height, image.format()); QPainter gc(&img); gc.fillRect(img.rect(), Qt::white); gc.setPen(Qt::NoPen); gc.setBrush(QBrush(image)); gc.drawRect(img.rect()); image = img; } // Only convert to grayscale if it is rgb. Otherwise, it's gray already. if (d->grayscalePreview && !image.isGrayscale()) { QRgb *pixel = reinterpret_cast(image.bits()); for (int row = 0; row < image.height(); ++row) { for (int col = 0; col < image.width(); ++col) { const QRgb currentPixel = pixel[row * image.width() + col]; const int red = qRed(currentPixel); const int green = qGreen(currentPixel); const int blue = qBlue(currentPixel); const int grayValue = (red * 11 + green * 16 + blue * 5) / 32; pixel[row * image.width() + col] = qRgb(grayValue, grayValue, grayValue); } } } d->previewLabel->setPixmap(QPixmap::fromImage(image)); } KoResourceSP KisResourceItemChooser::resourceFromModelIndex(const QModelIndex &index) const { if (!index.isValid()) { return 0; } KoResourceSP r = d->tagFilterProxyModel->resourceForIndex(index); return r; } QSize KisResourceItemChooser::viewSize() const { return d->view->size(); } KisResourceItemListView *KisResourceItemChooser::itemView() const { return d->view; } void KisResourceItemChooser::contextMenuRequested(const QPoint &pos) { d->tagManager->contextMenuRequested(currentResource(), pos); } void KisResourceItemChooser::setViewModeButtonVisible(bool visible) { d->viewModeButton->setVisible(visible); } QToolButton *KisResourceItemChooser::viewModeButton() const { return d->viewModeButton; } void KisResourceItemChooser::setSynced(bool sync) { if (d->synced == sync) return; d->synced = sync; KisResourceItemChooserSync *chooserSync = KisResourceItemChooserSync::instance(); if (sync) { connect(chooserSync, SIGNAL(baseLengthChanged(int)), SLOT(baseLengthChanged(int))); baseLengthChanged(chooserSync->baseLength()); } else { chooserSync->disconnect(this); } } void KisResourceItemChooser::baseLengthChanged(int length) { if (d->synced) { d->view->setItemSize(QSize(length, length)); } } bool KisResourceItemChooser::eventFilter(QObject *object, QEvent *event) { if (d->synced && event->type() == QEvent::Wheel) { KisResourceItemChooserSync *chooserSync = KisResourceItemChooserSync::instance(); QWheelEvent *qwheel = static_cast(event); if (qwheel->modifiers() & Qt::ControlModifier) { int degrees = qwheel->delta() / 8; int newBaseLength = chooserSync->baseLength() + degrees / 15 * 10; chooserSync->setBaseLength(newBaseLength); return true; } } return QObject::eventFilter(object, event); } void KisResourceItemChooser::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); updateView(); } void KisResourceItemChooser::showEvent(QShowEvent *event) { QWidget::showEvent(event); updateView(); } void KisResourceItemChooser::updateView() { if (d->synced) { KisResourceItemChooserSync *chooserSync = KisResourceItemChooserSync::instance(); baseLengthChanged(chooserSync->baseLength()); } /// helps to set icons here in case the theme is changed d->viewModeButton->setIcon(koIcon("view-choose")); d->importButton->setIcon(koIcon("document-open")); d->deleteButton->setIcon(koIcon("trash-empty")); d->storagePopupButton->setIcon(koIcon("bundle_archive")); } diff --git a/libs/resourcewidgets/KisResourceItemDelegate.cpp b/libs/resourcewidgets/KisResourceItemDelegate.cpp index 7599a95c4b..66f62be6b2 100644 --- a/libs/resourcewidgets/KisResourceItemDelegate.cpp +++ b/libs/resourcewidgets/KisResourceItemDelegate.cpp @@ -1,89 +1,89 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * Copyright (C) 2018 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 "KisResourceItemDelegate.h" #include #include #include "KisResourceModel.h" KisResourceItemDelegate::KisResourceItemDelegate(QObject *parent) : QAbstractItemDelegate(parent) , m_checkerPainter(4) { } void KisResourceItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & option, const QModelIndex &index) const { if (!index.isValid()) return; painter->save(); if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.highlight()); } QRect innerRect = option.rect.adjusted(2, 2, -2, -2); - QImage thumbnail = index.data(Qt::UserRole + KisResourceModel::Image).value(); + QImage thumbnail = index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); QSize imageSize = thumbnail.size(); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); QString resourceType = index.data(Qt::UserRole + KisResourceModel::ResourceType).toString(); // XXX: don't use a hardcoded string here to identify the resource type if (resourceType == ResourceType::Gradients) { m_checkerPainter.paint(*painter, innerRect); thumbnail = thumbnail.scaled(innerRect.width(), innerRect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter->drawImage(innerRect.topLeft(), thumbnail); } else if (resourceType == ResourceType::Patterns) { painter->fillRect(innerRect, Qt::white); // no checkers, they are confusing with patterns. if (imageSize.height() > innerRect.height() || imageSize.width() > innerRect.width()) { thumbnail = thumbnail.scaled(innerRect.width(), innerRect.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); } painter->fillRect(innerRect, QBrush(thumbnail)); } else { painter->fillRect(innerRect, Qt::white); // no checkers, they are confusing with patterns. if (imageSize.height() > innerRect.height() || imageSize.width() > innerRect.width()) { thumbnail = thumbnail.scaled(innerRect.width(), innerRect.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); } QPoint topleft(innerRect.topLeft()); if (thumbnail.width() < innerRect.width()) { topleft.setX(topleft.x() + (innerRect.width() - thumbnail.width()) / 2); } if (thumbnail.height() < innerRect.height()) { topleft.setY(topleft.y() + (innerRect.height() - thumbnail.height()) / 2); } painter->drawImage(topleft, thumbnail); } painter->restore(); } QSize KisResourceItemDelegate::sizeHint(const QStyleOptionViewItem &optionItem, const QModelIndex &) const { return optionItem.decorationSize; } diff --git a/libs/resourcewidgets/KisResourceTaggingManager.cpp b/libs/resourcewidgets/KisResourceTaggingManager.cpp index dd5d31e659..d5bf75fa3a 100644 --- a/libs/resourcewidgets/KisResourceTaggingManager.cpp +++ b/libs/resourcewidgets/KisResourceTaggingManager.cpp @@ -1,185 +1,190 @@ /* * This file is part of the KDE project * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Jan Hambrecht * Copyright (c) 2007 Sven Langkamp * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * Copyright (c) 2013 Sascha Suelzer * Copyright (c) 2020 Agata Cacko * 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 "KisResourceTaggingManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisTagFilterWidget.h" #include "KisTagChooserWidget.h" #include "KisResourceItemChooserContextMenu.h" #include "kis_debug.h" #include "KisTag.h" class TaggedResourceSet { public: TaggedResourceSet() {} TaggedResourceSet(const QString& tagName, const QList& resources) : tagName(tagName) , resources(resources) {} QString tagName; QList resources; }; class KisResourceTaggingManager::Private { public: KisTagSP currentTag; KisTagChooserWidget *tagChooser; KisTagFilterWidget *tagFilter; QCompleter *tagCompleter; QPointer model; KisTagModel* tagModel; KisResourceModel* resourceSourceModel; }; KisResourceTaggingManager::KisResourceTaggingManager(QString resourceType, KisTagFilterResourceProxyModel *model, QWidget *parent) : QObject(parent) , d(new Private()) { d->model = model; d->tagModel = KisTagModelProvider::tagModel(resourceType); d->resourceSourceModel = KisResourceModelProvider::resourceModel(resourceType); d->tagChooser = new KisTagChooserWidget(d->tagModel, parent); d->tagFilter = new KisTagFilterWidget(d->tagModel, parent); connect(d->tagChooser, SIGNAL(sigTagChosen(KisTagSP)), this, SLOT(tagChooserIndexChanged(KisTagSP))); - connect(d->tagFilter, SIGNAL(saveButtonClicked()), this, SLOT(tagSaveButtonPressed())); + connect(d->tagFilter, SIGNAL(filterByTagChanged(bool)), this, SLOT(slotFilterByTagChanged(bool))); connect(d->tagFilter, SIGNAL(filterTextChanged(QString)), this, SLOT(tagSearchLineEditTextChanged(QString))); + } KisResourceTaggingManager::~KisResourceTaggingManager() { delete d; } void KisResourceTaggingManager::showTaggingBar(bool show) { show ? d->tagFilter->show() : d->tagFilter->hide(); show ? d->tagChooser->show() : d->tagChooser->hide(); } void KisResourceTaggingManager::tagChooserIndexChanged(const KisTagSP tag) { ENTER_FUNCTION(); d->model->setTag(tag); d->currentTag = tag; d->tagFilter->clear(); - d->tagFilter->allowSave(tag->id() >= 0); // disallow save if the chosen tag has negative id (i.e. 'All' tag) } void KisResourceTaggingManager::tagSearchLineEditTextChanged(const QString& lineEditText) { fprintf(stderr, "void KisResourceTaggingManager::tagSearchLineEditTextChanged(const QString& lineEditText): %s \n", lineEditText.toStdString().c_str()); d->model->setSearchBoxText(lineEditText); ENTER_FUNCTION() << ppVar(lineEditText); } +void KisResourceTaggingManager::slotFilterByTagChanged(const bool filterByTag) +{ + d->model->setFilterByCurrentTag(filterByTag); +} + void KisResourceTaggingManager::tagSaveButtonPressed() { fprintf(stderr, "void KisResourceTaggingManager::tagSaveButtonPressed()\n"); KisTagSP tag = d->tagChooser->currentlySelectedTag(); // untag all previous resources int allResources = d->resourceSourceModel->rowCount(); for (int i = 0; i < allResources; i++) { QModelIndex index = d->resourceSourceModel->index(i, 0); KoResourceSP resource = d->resourceSourceModel->resourceForIndex(index); QVector tags = d->resourceSourceModel->tagsForResource(resource->resourceId()); QVector::iterator iter = std::find_if(tags.begin(), tags.end(), [tag](KisTagSP tagFromResource) { return tagFromResource->url() == tag->url(); }); if (iter != tags.end()) { d->tagModel->untagResource(tag, resource); } } // tag all resources that are here now int rows = d->model->rowCount(); for (int i = 0; i < rows; i++) { QModelIndex index = d->model->index(i, 0); KoResourceSP resource = d->model->resourceForIndex(index); if (!tag.isNull() && !resource.isNull()) { d->tagModel->tagResource(tag, resource); } } ENTER_FUNCTION(); } void KisResourceTaggingManager::contextMenuRequested(KoResourceSP resource, QPoint pos) { ENTER_FUNCTION(); // No visible tag chooser usually means no intended tag interaction, // context menu makes no sense then either fprintf(stderr, "context menu requested!"); if (!resource || !d->tagChooser->isVisible()) return; KisResourceItemChooserContextMenu menu(resource, d->tagChooser->currentlySelectedTag()); menu.exec(pos); } KisTagChooserWidget *KisResourceTaggingManager::tagChooserWidget() { return d->tagChooser; } KisTagFilterWidget *KisResourceTaggingManager::tagFilterWidget() { return d->tagFilter; } diff --git a/libs/resourcewidgets/KisResourceTaggingManager.h b/libs/resourcewidgets/KisResourceTaggingManager.h index 35ce012312..3fda86e4c7 100644 --- a/libs/resourcewidgets/KisResourceTaggingManager.h +++ b/libs/resourcewidgets/KisResourceTaggingManager.h @@ -1,83 +1,84 @@ /* * This file is part of the KDE project * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Jan Hambrecht * Copyright (c) 2007 Sven Langkamp * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * Copyright (c) 2013 Sascha Suelzer * Copyright (c) 2019 Boudewijn Rempt * Copyright (c) 2020 Agata Cacko * * 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 KISRESOURCETAGGINGMANAGER_H #define KISRESOURCETAGGINGMANAGER_H #include #include #include #include #include #include class QWidget; class QStringList; class QString; class QPoint; class KisTagFilterWidget; class KisTagChooserWidget; class KisTagFilterResourceProxyModel; /** * @brief The KisResourceTaggingManager class is ... * * XXX: this needs to be documented! */ class KisResourceTaggingManager : public QObject { Q_OBJECT public: explicit KisResourceTaggingManager(QString resourceType, KisTagFilterResourceProxyModel *model, QWidget *parent); ~KisResourceTaggingManager() override; void showTaggingBar(bool show); void contextMenuRequested(KoResourceSP currentResource, QPoint pos); KisTagFilterWidget *tagFilterWidget(); KisTagChooserWidget *tagChooserWidget(); private Q_SLOTS: void tagSaveButtonPressed(); void tagChooserIndexChanged(const KisTagSP lineEditText); void tagSearchLineEditTextChanged(const QString &lineEditText); + void slotFilterByTagChanged(const bool filterByTag); private: void enableContextMenu(bool enable); class Private; Private* const d; }; #endif // KORESOURCETAGGINGINTERFACE_H diff --git a/libs/resourcewidgets/KisTagFilterWidget.cpp b/libs/resourcewidgets/KisTagFilterWidget.cpp index 2ed858ab27..a49fcccf1b 100644 --- a/libs/resourcewidgets/KisTagFilterWidget.cpp +++ b/libs/resourcewidgets/KisTagFilterWidget.cpp @@ -1,147 +1,139 @@ /* * This file is part of the KDE project * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Jan Hambrecht * Copyright (c) 2007 Sven Langkamp * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * Copyright (c) 2013 Sascha Suelzer * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisTagFilterWidget.h" #include #include #include #include #include +#include #include #include #include +#include +#include +#include class KisTagFilterWidget::Private { public: QString tagSearchBarTooltip_saving_disabled; QString tagSearchBarTooltip_saving_enabled; QLineEdit* tagSearchLineEdit; - QPushButton* tagSearchSaveButton; QGridLayout* filterBarLayout; QCompleter* completer; + QCheckBox* filterByTagCheckbox; + + QString configGroup {"resources"}; + QString configName {"filterByTagChecked"}; + }; KisTagFilterWidget::KisTagFilterWidget(KisTagModel* model, QWidget* parent) : QWidget(parent) , d(new Private()) { QString searchTooltipMaintext = i18nc( "@info:tooltip", "

Enter search terms here to add resources to, or remove them from, the current tag view.

" "

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

" "

To include or exclude other tag sets:
" "[Tagname] or ![Tagname]

" "

For case sensitive and full name matching in-/exclusion:
" "\"ExactMatch\" or !\"ExactMatch\"

"); d->tagSearchBarTooltip_saving_disabled = searchTooltipMaintext + i18nc( "@info:tooltip", "

Filter results cannot be saved for the All Presets view. " "In this view, pressing Enter or clearing the filter box will restore all items. " "Create and/or switch to a different tag if you want to save filtered resources into named sets.

"); d->tagSearchBarTooltip_saving_enabled = searchTooltipMaintext + i18nc( "@info:tooltip", "

Pressing Enter or clicking the Save button will save the changes.

"); QGridLayout* filterBarLayout = new QGridLayout; d->tagSearchLineEdit = new QLineEdit(this); d->tagSearchLineEdit->setClearButtonEnabled(true); d->tagSearchLineEdit->setPlaceholderText(i18n("Search")); d->tagSearchLineEdit->setToolTip(d->tagSearchBarTooltip_saving_disabled); d->tagSearchLineEdit->setEnabled(true); d->completer = new QCompleter(model, this); d->completer->setCompletionRole(Qt::DisplayRole); d->completer->setCaseSensitivity(Qt::CaseInsensitive); d->tagSearchLineEdit->setCompleter(d->completer); - filterBarLayout->setSpacing(0); filterBarLayout->setMargin(0); filterBarLayout->setColumnStretch(0, 1); filterBarLayout->addWidget(d->tagSearchLineEdit, 0, 0); - d->tagSearchSaveButton = new QPushButton(this); - d->tagSearchSaveButton->setIcon(koIcon("media-floppy")); - d->tagSearchSaveButton->setToolTip(i18nc("@info:tooltip", "Save the currently filtered set as the new members of the current tag.")); - d->tagSearchSaveButton->setEnabled(false); + d->filterByTagCheckbox = new QCheckBox(this); + d->filterByTagCheckbox->setText(i18nc("It appears in the checkbox next to the filter box " + "in resources dockers; must be short.", "filter by tag")); + + KConfigGroup cfg = KSharedConfig::openConfig()->group(d->configGroup); + bool filterByTagCheckboxChecked = cfg.readEntry(d->configName, true); + d->filterByTagCheckbox->setChecked(filterByTagCheckboxChecked); - filterBarLayout->addWidget(d->tagSearchSaveButton, 0, 1); - connect(d->tagSearchSaveButton, SIGNAL(pressed()), - this, SLOT(onSaveButtonClicked())); - connect(d->tagSearchLineEdit, SIGNAL(returnPressed()), - this, SLOT(onSaveButtonClicked())); + filterBarLayout->addWidget(d->filterByTagCheckbox, 0, 1); connect(d->tagSearchLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onTextChanged(QString))); - allowSave(false); + connect(d->filterByTagCheckbox, SIGNAL(stateChanged(int)), this, SLOT(slotFilterByTagChanged(int))); this->setLayout(filterBarLayout); } KisTagFilterWidget::~KisTagFilterWidget() { delete d; } -void KisTagFilterWidget::allowSave(bool allow) -{ - if (allow) { - d->tagSearchSaveButton->show(); - d->tagSearchLineEdit->setToolTip(d->tagSearchBarTooltip_saving_enabled); - } - else { - d->tagSearchSaveButton->hide(); - d->tagSearchLineEdit->setToolTip(d->tagSearchBarTooltip_saving_disabled); - } -} void KisTagFilterWidget::clear() { d->tagSearchLineEdit->clear(); - d->tagSearchSaveButton->setEnabled(false); } void KisTagFilterWidget::onTextChanged(const QString& lineEditText) { - ENTER_FUNCTION() << ppVar(lineEditText); - d->tagSearchSaveButton->setEnabled(!lineEditText.isEmpty()); emit filterTextChanged(lineEditText); } -void KisTagFilterWidget::onSaveButtonClicked() +void KisTagFilterWidget::slotFilterByTagChanged(int filterByTag) { - ENTER_FUNCTION() << ppVar(d->tagSearchLineEdit->text()); - emit saveButtonClicked(); - clear(); + emit filterByTagChanged(filterByTag == Qt::Checked); + KConfigGroup cfg = KSharedConfig::openConfig()->group(d->configGroup); + cfg.writeEntry(d->configName, filterByTag == Qt::Checked); } diff --git a/libs/resourcewidgets/KisTagFilterWidget.h b/libs/resourcewidgets/KisTagFilterWidget.h index 5daec84f72..f3e000a219 100644 --- a/libs/resourcewidgets/KisTagFilterWidget.h +++ b/libs/resourcewidgets/KisTagFilterWidget.h @@ -1,54 +1,53 @@ /* * This file is part of the KDE project * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Jan Hambrecht * Copyright (c) 2007 Sven Langkamp * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * Copyright (c) 2013 Sascha Suelzer * Copyright (c) 2019 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 KISTAGFILTERWIDGET_H #define KISTAGFILTERWIDGET_H #include #include class KisTagFilterWidget : public QWidget { Q_OBJECT public: explicit KisTagFilterWidget(KisTagModel* model, QWidget* parent); ~KisTagFilterWidget() override; - void allowSave(bool allow); void clear(); Q_SIGNALS: void filterTextChanged(const QString &filterText); - void saveButtonClicked(); + void filterByTagChanged(const bool filterByTag); private Q_SLOTS: void onTextChanged(const QString &lineEditText); - void onSaveButtonClicked(); + void slotFilterByTagChanged(int filterByTag); private: class Private; Private* const d; }; #endif // KOTAGFILTERWIDGET_H diff --git a/libs/resourcewidgets/dbexplorer/DlgDbExplorer.cpp b/libs/resourcewidgets/dbexplorer/DlgDbExplorer.cpp index 8dca03eb52..3033579b0a 100644 --- a/libs/resourcewidgets/dbexplorer/DlgDbExplorer.cpp +++ b/libs/resourcewidgets/dbexplorer/DlgDbExplorer.cpp @@ -1,226 +1,211 @@ /* * Copyright (c) 2018 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 "DlgDbExplorer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include DlgDbExplorer::DlgDbExplorer(QWidget *parent) : KoDialog(parent) { setCaption(i18n("Resources Cache Database Explorer")); setButtons(Ok); m_page = new WdgDbExplorer(this); Q_CHECK_PTR(m_page); setMainWidget(m_page); m_resourceTypeModel = new KisResourceTypeModel(this); m_tagModel = new KisTagModel("", this); { - TableModel *storagesModel = new TableModel(this, QSqlDatabase::database()); - TableDelegate *storagesDelegate = new TableDelegate(m_page->tableStorages); - storagesModel->setTable("storages"); - storagesModel->setHeaderData(0, Qt::Horizontal, i18n("Id")); - storagesModel->setHeaderData(1, Qt::Horizontal, i18n("Type")); - storagesModel->setRelation(1, QSqlRelation("storage_types", "id", "name")); - storagesModel->setHeaderData(2, Qt::Horizontal, i18n("Location")); - storagesModel->setHeaderData(3, Qt::Horizontal, i18n("Creation Date")); - storagesModel->addDateTimeColumn(3); - storagesDelegate->addDateTimeColumn(3); - storagesModel->setHeaderData(4, Qt::Horizontal, i18n("Preinstalled")); - storagesModel->addBooleanColumn(4); - storagesDelegate->addBooleanColumn(4); - storagesModel->setHeaderData(5, Qt::Horizontal, i18n("Active")); - storagesModel->addBooleanColumn(5); - storagesDelegate->addBooleanColumn(5); - storagesModel->select(); - m_page->tableStorages->setModel(storagesModel); + m_page->tableStorages->setModel(new KisStorageModel(this)); m_page->tableStorages->hideColumn(0); - m_page->tableStorages->setItemDelegate(storagesDelegate); m_page->tableStorages->setSelectionMode(QAbstractItemView::SingleSelection); m_page->tableStorages->resizeColumnsToContents(); } { KisResourceModel *resourcesModel = KisResourceModelProvider::resourceModel(ResourceType::Brushes); m_page->tableResources->setModel(resourcesModel); m_page->tableResources->hideColumn(0); m_page->tableResources->setSelectionMode(QAbstractItemView::SingleSelection); m_page->cmbResourceTypes->setModel(m_resourceTypeModel); m_page->cmbResourceTypes->setModelColumn(KisResourceTypeModel::Name); connect(m_page->cmbResourceTypes, SIGNAL(activated(int)), SLOT(slotTbResourceTypeSelected(int))); connect(m_page->tableResources, SIGNAL(clicked(QModelIndex)), SLOT(slotTbResourceItemSelected())); } { TableModel *tagsModel = new TableModel(this, QSqlDatabase::database()); TableDelegate *tagsDelegate = new TableDelegate(m_page->tableStorages); tagsDelegate->setEditable(true); tagsModel->setTable("tags"); tagsModel->setHeaderData(0, Qt::Horizontal, i18n("Id")); tagsModel->setHeaderData(1, Qt::Horizontal, i18n("Type")); tagsModel->setRelation(1, QSqlRelation("resource_types", "id", "name")); tagsModel->setHeaderData(2, Qt::Horizontal, i18n("Tag")); tagsModel->setHeaderData(3, Qt::Horizontal, i18n("Name")); tagsModel->setHeaderData(4, Qt::Horizontal, i18n("Comment")); tagsModel->setHeaderData(5, Qt::Horizontal, i18n("Active")); + tagsModel->setHeaderData(6, Qt::Horizontal, i18n("Thumbnail")); + tagsModel->setHeaderData(7, Qt::Horizontal, i18n("Display Name")); tagsModel->addBooleanColumn(5); tagsDelegate->addBooleanColumn(5); tagsModel->select(); connect(tagsModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(slotResetTagModel(QModelIndex, QModelIndex))); m_page->tableTags->setModel(tagsModel); m_page->tableTags->hideColumn(0); m_page->tableTags->setItemDelegate(tagsDelegate); m_page->tableTags->setSelectionMode(QAbstractItemView::SingleSelection); m_page->tableTags->resizeColumnsToContents(); } { QSqlTableModel *versionModel = new QSqlTableModel(this, QSqlDatabase::database()); versionModel->setTable("version_information"); versionModel->setHeaderData(0, Qt::Horizontal, "id"); versionModel->setHeaderData(1, Qt::Horizontal, "database_version"); versionModel->setHeaderData(2, Qt::Horizontal, "krita_version"); versionModel->setHeaderData(3, Qt::Horizontal, "creation_date"); versionModel->select(); QSqlRecord r = versionModel->record(0); m_page->lblDatabaseVersion->setText(r.value("database_version").toString()); m_page->lblKritaVersion->setText(r.value("krita_version").toString()); m_page->lblCreationDate->setText(r.value("creation_date").toString()); } { m_page->cmbRvResourceTypes->setModel(m_resourceTypeModel); m_page->cmbRvResourceTypes->setModelColumn(KisResourceTypeModel::Name); connect(m_page->cmbRvResourceTypes, SIGNAL(activated(int)), SLOT(slotRvResourceTypeSelected(int))); m_page->cmbRvTags->setModelColumn(KisTagModel::Name); m_page->cmbRvTags->setModel(m_tagModel); connect(m_page->cmbRvTags, SIGNAL(activated(int)), SLOT(slotRvTagSelected(int))); m_page->cmbRvResourceTypes->setCurrentIndex(0); slotRvResourceTypeSelected(0); m_page->resourceItemView->setItemDelegate(new KisResourceItemDelegate(this)); m_page->resourceItemView->setSelectionMode(QAbstractItemView::SingleSelection); } } DlgDbExplorer::~DlgDbExplorer() { } void DlgDbExplorer::updateTagModel(const QString& resourceType) { m_tagModel = KisTagModelProvider::tagModel(resourceType); m_page->cmbRvTags->setModelColumn(KisTagModel::Name); m_page->cmbRvTags->setModel(m_tagModel); m_page->cmbRvTags->update(); qDebug() << "number of tags in " << resourceType << " tag model: " << m_tagModel->rowCount(); } void DlgDbExplorer::slotResetTagModel(QModelIndex topLeft, QModelIndex bottomRight) { KisTagModelProvider::resetModels(); } void DlgDbExplorer::slotRvResourceTypeSelected(int index) { QModelIndex idx = m_page->cmbResourceTypes->model()->index(index, KisResourceTypeModel::ResourceType); QString resourceType = idx.data(Qt::DisplayRole).toString(); qDebug() << resourceType; updateTagModel(resourceType); KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(resourceType); KisTagFilterResourceProxyModel *tagFilterModel = new KisTagFilterResourceProxyModel(KisTagModelProvider::tagModel(resourceType), this); tagFilterModel->setSourceModel(resourceModel); m_filterProxyModel = tagFilterModel; m_page->resourceItemView->setModel(tagFilterModel); } void DlgDbExplorer::slotTbResourceTypeSelected(int index) { QModelIndex idx = m_page->cmbRvResourceTypes->model()->index(index, KisResourceTypeModel::ResourceType); QString resourceType = idx.data(Qt::DisplayRole).toString(); qDebug() << resourceType; m_tagModel = KisTagModelProvider::tagModel(resourceType); KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(resourceType); m_page->tableResources->setModel(resourceModel); m_page->tableResources->setCurrentIndex(m_page->tableResources->model()->index(0, 0)); slotTbResourceItemSelected(); m_page->tableResources->resizeColumnsToContents(); } void DlgDbExplorer::slotTbResourceItemSelected() { QModelIndex idx = m_page->tableResources->selectionModel()->selectedIndexes().first(); - QImage thumb = idx.data(Qt::UserRole + KisResourceModel::Image).value(); + QImage thumb = idx.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); Qt::TransformationMode mode = Qt::SmoothTransformation; if (thumb.size().width() < 100 && thumb.size().height() < 100) { mode = Qt::FastTransformation; } m_page->lblThumbnail->setPixmap(QPixmap::fromImage(thumb.scaled(100, 100, Qt::KeepAspectRatio, mode))); //If we could get a list of versions for a given resource, this would be the moment to add them... } void DlgDbExplorer::slotRvTagSelected(int index) { qDebug() << "selected tag" << index; QModelIndex idx = m_tagModel->index(index, 0); KisTagSP tag = m_tagModel->tagForIndex(idx); if (m_filterProxyModel && !tag.isNull() && tag->valid()) { m_filterProxyModel->setTag(tag); } } diff --git a/libs/resourcewidgets/dbexplorer/TableModel.cpp b/libs/resourcewidgets/dbexplorer/TableModel.cpp index d5e2570f6d..9df2094ab9 100644 --- a/libs/resourcewidgets/dbexplorer/TableModel.cpp +++ b/libs/resourcewidgets/dbexplorer/TableModel.cpp @@ -1,137 +1,175 @@ /* * Copyright (c) 2018 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 "TableModel.h" #include TableDelegate::TableDelegate(QObject *parent) : QSqlRelationalDelegate(parent) {} +QRect getNewRect(const QStyleOptionViewItem &option) +{ + // get the rectangle in the middle of the field + const int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; + QRect newRect = QStyle::alignedRect(option.direction, Qt::AlignCenter, + QSize(option.decorationSize.width() + + 5,option.decorationSize.height()), + QRect(option.rect.x() + textMargin, option.rect.y(), + option.rect.width() - + (2 * textMargin), option.rect.height())); + return newRect; +} + void TableDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem viewItemOption(option); // Only do this if we are accessing the column with boolean variables. if (m_booleanColumns.contains(index.column())) { // This basically changes the rectangle in which the check box is drawn. - const int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; - QRect newRect = QStyle::alignedRect(option.direction, Qt::AlignCenter, - QSize(option.decorationSize.width() + - 5,option.decorationSize.height()), - QRect(option.rect.x() + textMargin, option.rect.y(), - option.rect.width() - - (2 * textMargin), option.rect.height())); - viewItemOption.rect = newRect; + viewItemOption.rect = getNewRect(option); } // Draw the check box using the new rectangle. QSqlRelationalDelegate::paint(painter, viewItemOption, index); } QSize TableDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { return QSqlRelationalDelegate::sizeHint(option, index); } bool TableDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { if (m_editable) { - return QSqlRelationalDelegate::editorEvent(event, model, option, index); + if (m_booleanColumns.contains(index.column())) { + QStyleOptionViewItem optionCheckable = option; + optionCheckable.rect = getNewRect(option); + optionCheckable.features |= QStyleOptionViewItem::HasCheckIndicator; + return QSqlRelationalDelegate::editorEvent(event, model, optionCheckable, index); + } else { + return QSqlRelationalDelegate::editorEvent(event, model, option, index); + } } return false; } QWidget *TableDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (m_editable) { - return QSqlRelationalDelegate::createEditor(parent, option, index); + if (m_booleanColumns.contains(index.column())) { + QStyleOptionViewItem optionCheckable = option; + optionCheckable.features |= QStyleOptionViewItem::HasCheckIndicator; + optionCheckable.rect = getNewRect(option); + return QSqlRelationalDelegate::createEditor(parent, optionCheckable, index); + } else { + return QSqlRelationalDelegate::createEditor(parent, option, index); + } } return 0; } void TableDelegate::addDateTimeColumn(int column) { m_dateTimeColumns << column; } void TableDelegate::addBooleanColumn(int column) { m_booleanColumns << column; } void TableDelegate::setEditable(bool editable) { m_editable = editable; } TableModel::TableModel(QObject *parent, QSqlDatabase db) : QSqlRelationalTableModel(parent, db) { - + this->setEditStrategy(QSqlTableModel::OnFieldChange); } TableModel::~TableModel() { } QVariant TableModel::data(const QModelIndex &index, int role) const { QVariant d = QSqlRelationalTableModel::data(index, Qt::DisplayRole); if (role == Qt::DisplayRole) { if (m_dateTimeColumns.contains(index.column())) { d = QVariant::fromValue(QDateTime::fromSecsSinceEpoch(d.toInt()).toString()); } if (m_booleanColumns.contains(index.column())) { return QVariant(); } } else if (role == Qt::CheckStateRole) { if (m_booleanColumns.contains(index.column())) { if (d.toInt() == 0) { return Qt::Unchecked; } else { return Qt::Checked; } } return QVariant(); } return d; } +bool TableModel::setData(const QModelIndex & index, const QVariant & value, int role) +{ + if (m_booleanColumns.contains(index.column()) && role == Qt::CheckStateRole) { + // Writing the data when the check box is set to checked. + if (value == Qt::Checked) { + // Let's write the new value + return QSqlTableModel::setData(index, 1, Qt::EditRole); + // Writing the data when the check box is set to unchecked + } else if (value == Qt::Unchecked) { + // Let's write the new value + return QSqlTableModel::setData(index, 0, Qt::EditRole); + } + } + + bool response = QSqlTableModel::setData(index, value, role); + return response; +} + Qt::ItemFlags TableModel::flags(const QModelIndex &index) const { Qt::ItemFlags f = QSqlRelationalTableModel::flags((index)); if (m_booleanColumns.contains(index.column())) { f |= Qt::ItemIsUserCheckable; } return f; } void TableModel::addDateTimeColumn(int column) { m_dateTimeColumns << column; } void TableModel::addBooleanColumn(int column) { m_booleanColumns << column; } diff --git a/libs/resourcewidgets/dbexplorer/TableModel.h b/libs/resourcewidgets/dbexplorer/TableModel.h index 32d096c65b..4ad1a906ae 100644 --- a/libs/resourcewidgets/dbexplorer/TableModel.h +++ b/libs/resourcewidgets/dbexplorer/TableModel.h @@ -1,70 +1,71 @@ /* * Copyright (c) 2018 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 TABLEMODEL_H #define TABLEMODEL_H #include #include class TableDelegate : public QSqlRelationalDelegate { Q_OBJECT public: TableDelegate(QObject *parent); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void addDateTimeColumn(int column); void addBooleanColumn(int column); void setEditable(bool editable); private: bool m_editable{false}; QVector m_booleanColumns; QVector m_dateTimeColumns; }; /** * @brief The TableModel class handles boolean * and datatime columns in a custom way. */ class TableModel : public QSqlRelationalTableModel { Q_OBJECT public: TableModel(QObject *parent = nullptr, QSqlDatabase db = QSqlDatabase()); ~TableModel() override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex & index, const QVariant & value, int role) override; Qt::ItemFlags flags(const QModelIndex &index) const override; void addDateTimeColumn(int column); void addBooleanColumn(int column); private: QVector m_booleanColumns; QVector m_dateTimeColumns; }; #endif // TABLEMODEL_H diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 4233b0f04f..4c30c763c1 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,623 +1,622 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ) if (ANDROID) add_definitions(-DQT_OPENGL_ES_3) add_definitions(-DHAS_ONLY_OPENGL_ES) include_directories (${Qt5AndroidExtras_INCLUDE_DIRS}) endif() add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) find_library(APPKIT_LIBRARY AppKit) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp canvas/kis_display_color_converter.cpp canvas/kis_display_filter.cpp canvas/kis_exposure_gamma_correction_interface.cpp canvas/kis_tool_proxy.cpp canvas/kis_canvas_decoration.cc canvas/kis_coordinates_converter.cpp canvas/kis_grid_manager.cpp canvas/kis_grid_decoration.cpp canvas/kis_grid_config.cpp canvas/kis_prescaled_projection.cpp canvas/kis_qpainter_canvas.cpp canvas/kis_projection_backend.cpp canvas/kis_update_info.cpp canvas/kis_image_patch.cpp canvas/kis_image_pyramid.cpp canvas/kis_infinity_manager.cpp canvas/kis_change_guides_command.cpp canvas/kis_guides_decoration.cpp canvas/kis_guides_manager.cpp canvas/kis_guides_config.cpp canvas/kis_snap_config.cpp canvas/kis_snap_line_strategy.cpp canvas/KisSnapPointStrategy.cpp canvas/KisSnapPixelStrategy.cpp canvas/KisMirrorAxisConfig.cpp dialogs/kis_about_application.cpp dialogs/kis_dlg_adj_layer_props.cc dialogs/kis_dlg_adjustment_layer.cc dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_generator_layer.cpp dialogs/kis_dlg_file_layer.cpp dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_stroke_selection_properties.cpp dialogs/kis_dlg_image_properties.cc dialogs/kis_dlg_layer_properties.cc dialogs/kis_dlg_preferences.cc dialogs/slider_and_spin_box_sync.cpp dialogs/kis_dlg_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp dialogs/KisSessionManagerDialog.cpp dialogs/KisNewWindowLayoutDialog.cpp dialogs/KisDlgChangeCloneSource.cpp flake/kis_node_dummies_graph.cpp flake/kis_dummies_facade_base.cpp flake/kis_dummies_facade.cpp flake/kis_node_shapes_graph.cpp flake/kis_node_shape.cpp flake/kis_shape_controller.cpp flake/kis_shape_layer.cc flake/kis_shape_layer_canvas.cpp flake/kis_shape_selection.cpp flake/kis_shape_selection_canvas.cpp flake/kis_shape_selection_model.cpp flake/kis_take_all_shapes_command.cpp brushhud/kis_uniform_paintop_property_widget.cpp brushhud/kis_brush_hud.cpp brushhud/kis_round_hud_button.cpp brushhud/kis_dlg_brush_hud_config.cpp brushhud/kis_brush_hud_properties_list.cpp brushhud/kis_brush_hud_properties_config.cpp kis_aspect_ratio_locker.cpp kis_autogradient.cc kis_bookmarked_configurations_editor.cc kis_bookmarked_configurations_model.cc kis_bookmarked_filter_configurations_model.cc KisPaintopPropertiesBase.cpp kis_canvas_resource_provider.cpp kis_derived_resources.cpp kis_categories_mapper.cpp kis_categorized_list_model.cpp kis_categorized_item_delegate.cpp kis_clipboard.cc kis_config.cc KisOcioConfiguration.cpp kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_change_file_layer_command.h kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc KisImageBarrierLockerWithFeedback.cpp kis_image_manager.cc kis_image_view_converter.cpp kis_import_catcher.cc kis_layer_manager.cc kis_mask_manager.cc kis_mimedata.cpp kis_node_commands_adapter.cpp kis_node_manager.cpp kis_node_juggler_compressed.cpp kis_node_selection_adapter.cpp kis_node_insertion_adapter.cpp KisNodeDisplayModeAdapter.cpp kis_node_model.cpp kis_node_filter_proxy_model.cpp kis_model_index_converter_base.cpp kis_model_index_converter.cpp kis_model_index_converter_show_all.cpp kis_painting_assistant.cc kis_painting_assistants_decoration.cpp KisDecorationsManager.cpp kis_paintop_box.cc kis_paintop_option.cpp kis_paintop_options_model.cpp kis_paintop_settings_widget.cpp kis_popup_palette.cpp kis_png_converter.cpp kis_preference_set_registry.cpp KisResourceServerProvider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc KisSelectionActionsAdapter.cpp kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp KisActionPlugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp KisWelcomePageWidget.cpp KisChangeCloneLayersCommand.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp opengl/kis_opengl_shader_loader.cpp opengl/kis_texture_tile_info_pool.cpp opengl/KisOpenGLUpdateInfoBuilder.cpp opengl/KisOpenGLModeProber.cpp opengl/KisScreenInformationAdapter.cpp kis_fps_decoration.cpp tool/KisToolChangesTracker.cpp tool/KisToolChangesTrackerData.cpp tool/kis_selection_tool_helper.cpp tool/kis_selection_tool_config_widget_helper.cpp tool/kis_rectangle_constraint_widget.cpp tool/kis_shape_tool_helper.cpp tool/kis_tool.cc tool/kis_delegated_tool_policies.cpp tool/kis_tool_freehand.cc tool/kis_speed_smoother.cpp tool/kis_painting_information_builder.cpp tool/kis_stabilized_events_sampler.cpp tool/kis_tool_freehand_helper.cpp tool/kis_tool_multihand_helper.cpp tool/kis_figure_painting_tool_helper.cpp tool/KisAsyncronousStrokeUpdateHelper.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/KisStabilizerDelayedPaintHelper.cpp tool/KisStrokeSpeedMonitor.cpp tool/strokes/freehand_stroke.cpp tool/strokes/KisStrokeEfficiencyMeasurer.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp tool/strokes/KisFreehandStrokeInfo.cpp tool/strokes/KisMaskedFreehandStrokePainter.cpp tool/strokes/KisMaskingBrushRenderer.cpp tool/strokes/KisMaskingBrushCompositeOpFactory.cpp tool/strokes/move_stroke_strategy.cpp tool/KisSelectionToolFactoryBase.cpp tool/KisToolPaintFactoryBase.cpp widgets/kis_cmb_composite.cc widgets/kis_cmb_contour.cpp widgets/kis_cmb_gradient.cpp widgets/kis_paintop_list_widget.cpp widgets/kis_cmb_idlist.cc widgets/kis_color_space_selector.cc widgets/kis_advanced_color_space_selector.cc widgets/kis_cie_tongue_widget.cpp widgets/kis_tone_curve_widget.cpp widgets/kis_curve_widget.cpp widgets/kis_custom_image_widget.cc widgets/kis_image_from_clipboard_widget.cpp widgets/kis_double_widget.cc widgets/kis_filter_selector_widget.cc widgets/kis_gradient_chooser.cc widgets/kis_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_paintop_presets_save.cpp widgets/kis_paintop_preset_icon_library.cpp widgets/kis_pattern_chooser.cc widgets/kis_preset_chooser.cpp widgets/kis_progress_widget.cpp widgets/kis_selection_options.cc widgets/kis_scratch_pad.cpp widgets/kis_scratch_pad_event_filter.cpp widgets/kis_preset_selector_strip.cpp widgets/KisSelectionPropertySlider.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/kis_categorized_list_view.cpp widgets/kis_widget_chooser.cpp widgets/kis_tool_button.cpp widgets/kis_floating_message.cpp widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp widgets/kis_elided_label.cpp widgets/kis_stopgradient_slider_widget.cpp widgets/kis_preset_live_preview_view.cpp widgets/KisScreenColorPicker.cpp widgets/KoDualColorButton.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp widgets/KisLayerStyleAngleSelector.cpp widgets/KisMemoryReportButton.cpp widgets/KisDitherWidget.cpp KisPaletteEditor.cpp dialogs/KisDlgPaletteEditor.cpp widgets/KisNewsWidget.cpp widgets/KisGamutMaskToolbar.cpp utils/kis_document_aware_spin_box_unit_manager.cpp utils/KisSpinBoxSplineUnitConverter.cpp utils/KisClipboardUtil.cpp utils/KisDitherUtil.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_native_gesture_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp input/KisQtWidgetsTweaker.cpp input/KisInputActionGroup.cpp input/kis_zoom_and_rotate_action.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactories.cpp actions/KisTransformToolActivationCommand.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.cpp kis_gui_context_command.cpp kis_gui_context_command_p.cpp input/kis_tablet_debugger.cpp input/kis_input_profile_manager.cpp input/kis_input_profile.cpp input/kis_shortcut_configuration.cpp input/config/kis_input_configuration_page.cpp input/config/kis_edit_profiles_dialog.cpp input/config/kis_input_profile_model.cpp input/config/kis_input_configuration_page_item.cpp input/config/kis_action_shortcuts_model.cpp input/config/kis_input_type_delegate.cpp input/config/kis_input_mode_delegate.cpp input/config/kis_input_button.cpp input/config/kis_input_editor_delegate.cpp input/config/kis_mouse_input_editor.cpp input/config/kis_wheel_input_editor.cpp input/config/kis_key_input_editor.cpp processing/fill_processing_visitor.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisCloneDocumentStroke.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisImportExportManager.cpp KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoActionsUpdateManager.cpp KisView.cpp KisCanvasWindow.cpp KisImportExportErrorCode.cpp KisImportExportAdditionalChecks.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp - kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisSaveGroupVisitor.cpp KisWindowLayoutResource.cpp KisWindowLayoutManager.cpp KisSessionResource.cpp KisReferenceImagesDecoration.cpp KisReferenceImage.cpp flake/KisReferenceImagesLayer.cpp flake/KisReferenceImagesLayer.h KisMouseClickEater.cpp KisDecorationsWrapperLayer.cpp ) if(WIN32) # Private headers are needed for: # * KisDlgCustomTabletResolution # * KisScreenInformationAdapter include_directories(SYSTEM ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_win.cpp ) if (NOT USE_QT_TABLET_WINDOWS) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp input/wintab/kis_tablet_support_win8.cpp ) else() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} dialogs/KisDlgCustomTabletResolution.cpp ) endif() endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAsyncAnimationRendererBase.cpp KisAsyncAnimationCacheRenderer.cpp KisAsyncAnimationFramesSavingRenderer.cpp dialogs/KisAsyncAnimationRenderDialogBase.cpp dialogs/KisAsyncAnimationCacheRenderDialog.cpp dialogs/KisAsyncAnimationFramesSaveDialog.cpp canvas/kis_animation_player.cpp kis_animation_importer.cpp KisSyncedAudioPlayback.cpp KisFrameDataSerializer.cpp KisFrameCacheStore.cpp KisFrameCacheSwapper.cpp KisAbstractFrameCacheSwapper.cpp KisInMemoryFrameCacheSwapper.cpp input/wintab/drawpile_tablettester/tablettester.cpp input/wintab/drawpile_tablettester/tablettest.cpp ) if (UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_unix.cpp ) endif() if(APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} osx.mm ) endif() if (ANDROID) set (kritaui_LIB_SRCS ${kritaui_LIB_SRCS} KisAndroidFileManager.cpp) endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui widgets/KisDitherWidget.ui forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgbookmarkedconfigurationseditor.ui forms/wdgapplyprofile.ui forms/wdgcustompattern.ui forms/wdglayerproperties.ui forms/wdgcolorsettings.ui forms/wdgtabletsettings.ui forms/wdgcolorspaceselector.ui forms/wdgcolorspaceselectoradvanced.ui forms/wdgdisplaysettings.ui forms/kis_previewwidgetbase.ui forms/kis_matrix_widget.ui forms/wdgselectionoptions.ui forms/wdggeometryoptions.ui forms/wdgnewimage.ui forms/wdgimageproperties.ui forms/wdgmaskfromselection.ui forms/wdgmasksource.ui forms/wdgfilterdialog.ui forms/wdgmetadatamergestrategychooser.ui forms/wdgpaintoppresets.ui forms/wdgpaintopsettings.ui forms/wdgdlggeneratorlayer.ui forms/wdgdlgfilelayer.ui forms/wdgfilterselector.ui forms/wdgfilternodecreation.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgsavebrushpreset.ui forms/wdgpreseticonlibrary.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui forms/wdgsessionmanager.ui forms/wdgnewwindowlayout.ui forms/KisWelcomePage.ui forms/WdgDlgPaletteEditor.ui forms/KisNewsPage.ui forms/wdgGamutMaskToolbar.ui forms/wdgchangeclonesource.ui brushhud/kis_dlg_brush_hud_config.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui layerstyles/wdgKisLayerStyleAngleSelector.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui input/wintab/drawpile_tablettester/tablettest.ui ) if(WIN32) if(USE_QT_TABLET_WINDOWS) ki18n_wrap_ui(kritaui_LIB_SRCS dialogs/KisDlgCustomTabletResolution.ui ) else() ki18n_wrap_ui(kritaui_LIB_SRCS input/wintab/kis_screen_size_choice_dialog.ui ) endif() endif() add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils kritaresources ${PNG_LIBRARIES} LibExiv2::LibExiv2 ) if (ANDROID) target_link_libraries(kritaui GLESv3) target_link_libraries(kritaui Qt5::Gui) target_link_libraries(kritaui Qt5::AndroidExtras) endif() if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (NOT WIN32 AND NOT APPLE AND NOT ANDROID) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) target_link_libraries(kritaui ${APPKIT_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE AND NOT ANDROID) target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras) endif() if(X11_FOUND) target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES}) endif() target_include_directories(kritaui PUBLIC $ $ $ $ $ $ $ ) set_target_properties(kritaui PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS}) if (APPLE) install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita) endif () diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index 13bf1b372d..e37bffdabe 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,930 +1,933 @@ /* * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #ifdef Q_OS_MACOS #include "osx.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoConfig.h" -#include #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include -#include "kis_md5_generator.h" #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" #include "kis_spin_box_unit_manager.h" #include "kis_document_aware_spin_box_unit_manager.h" #include "KisViewManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/KisScreenColorPicker.h" #include "KisDlgInternalColorSelector.h" #include #include +#include + namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplication::Private { public: Private() {} QPointer splashScreen; KisAutoSaveRecoveryDialog *autosaveDialog {0}; QPointer mainWindow; // The first mainwindow we create on startup bool batchRun {false}; QVector earlyRemoteArguments; }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash, int fileCount) : m_splash(splash) , m_fileCount(fileCount) { } ~ResetStarting() { if (m_splash) { m_splash->hide(); } } QPointer m_splash; int m_fileCount; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) , d(new Private) { #ifdef Q_OS_MACOS setMouseCoalescingEnabled(false); #endif QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); setApplicationDisplayName("Krita"); setApplicationName("krita"); // Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird. // setOrganizationName("krita"); setOrganizationDomain("krita.org"); QString version = KritaVersionWrapper::versionString(true); setApplicationVersion(version); setWindowIcon(KisIconUtils::loadIcon("krita")); if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { QStringList styles = QStringList() << "breeze" << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void KisApplication::initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void KisApplication::addResourceTypes() { // qDebug() << "addResourceTypes();"; // All Krita's resource types KoResourcePaths::addResourceType("markers", "data", "/styles/"); KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType(ResourceType::Brushes, "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType(ResourceType::PaintOpPresets, "data", "/paintoppresets/"); KoResourcePaths::addResourceType(ResourceType::Workspaces, "data", "/workspaces/"); KoResourcePaths::addResourceType(ResourceType::WindowLayouts, "data", "/windowlayouts/"); KoResourcePaths::addResourceType(ResourceType::Sessions, "data", "/sessions/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType(ResourceType::Patterns, "data", "/patterns/", true); KoResourcePaths::addResourceType(ResourceType::Gradients, "data", "/gradients/"); KoResourcePaths::addResourceType(ResourceType::Gradients, "data", "/gradients/", true); KoResourcePaths::addResourceType(ResourceType::Palettes, "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("kis_actions", "data", "/pykrita"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType(ResourceType::FilterEffects, "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); KoResourcePaths::addResourceType(ResourceType::Symbols, "data", "/symbols"); KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons"); KoResourcePaths::addResourceType(ResourceType::GamutMasks, "data", "/gamutmasks/", true); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir(ResourceType::Gradients, "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Gradients, QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Patterns, "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Patterns, QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Brushes, "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Brushes, QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Palettes, "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir(ResourceType::Palettes, QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/brushes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); // between 4.2.x and 4.3.0 there was a change from 'taskset' to 'tasksets' // so to make older resource folders compatible with the new version, let's rename the folder // so no tasksets are lost. if (d.exists(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/")) { d.rename(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/", QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tasksets/"); } d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tasksets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/color-schemes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gamutmasks/"); } bool KisApplication::registerResources() { KisResourceLoaderRegistry *reg = KisResourceLoaderRegistry::instance(); reg->add(new KisResourceLoader(ResourceType::PaintOpPresets, ResourceType::PaintOpPresets, i18n("Brush presets"), QStringList() << "application/x-krita-paintoppreset")); reg->add(new KisResourceLoader(ResourceSubType::GbrBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/x-gimp-brush")); reg->add(new KisResourceLoader(ResourceSubType::GihBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/x-gimp-brush-animated")); reg->add(new KisResourceLoader(ResourceSubType::SvgBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceSubType::PngBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/png")); reg->add(new KisResourceLoader(ResourceSubType::SegmentedGradients, ResourceType::Gradients, i18n("Gradients"), QStringList() << "application/x-gimp-gradient")); reg->add(new KisResourceLoader(ResourceSubType::StopGradients, ResourceType::Gradients, i18n("Gradients"), QStringList() << "application/x-karbon-gradient" << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceType::Palettes, ResourceType::Palettes, i18n("Palettes"), QStringList() << KisMimeDatabase::mimeTypeForSuffix("kpl") << KisMimeDatabase::mimeTypeForSuffix("gpl") << KisMimeDatabase::mimeTypeForSuffix("pal") << KisMimeDatabase::mimeTypeForSuffix("act") << KisMimeDatabase::mimeTypeForSuffix("aco") << KisMimeDatabase::mimeTypeForSuffix("css") << KisMimeDatabase::mimeTypeForSuffix("colors") << KisMimeDatabase::mimeTypeForSuffix("xml") << KisMimeDatabase::mimeTypeForSuffix("sbz"))); QList src = QImageReader::supportedMimeTypes(); QStringList allImageMimes; Q_FOREACH(const QByteArray ba, src) { if (QImageWriter::supportedMimeTypes().contains(ba)) { allImageMimes << QString::fromUtf8(ba); } } allImageMimes << KisMimeDatabase::mimeTypeForSuffix("pat"); reg->add(new KisResourceLoader(ResourceType::Patterns, ResourceType::Patterns, i18n("Patterns"), allImageMimes)); reg->add(new KisResourceLoader(ResourceType::Workspaces, ResourceType::Workspaces, i18n("Workspaces"), QStringList() << "application/x-krita-workspace")); reg->add(new KisResourceLoader(ResourceType::Symbols, ResourceType::Symbols, i18n("SVG symbol libraries"), QStringList() << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceType::WindowLayouts, ResourceType::WindowLayouts, i18n("Window layouts"), QStringList() << "application/x-krita-windowlayout")); reg->add(new KisResourceLoader(ResourceType::Sessions, ResourceType::Sessions, i18n("Sessions"), QStringList() << "application/x-krita-session")); reg->add(new KisResourceLoader(ResourceType::GamutMasks, ResourceType::GamutMasks, i18n("Gamut masks"), QStringList() << "application/x-krita-gamutmasks")); + reg->add(new KisResourceLoader(ResourceType::LayerStyles, + ResourceType::LayerStyles, + ResourceType::LayerStyles, + QStringList() << "application/x-photoshop-style")); + if (!KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) { QMessageBox::critical(0, i18nc("@title:window", "Krita: Fatal error"), i18n("%1\n\nKrita will quit now.").arg(KisResourceCacheDb::lastError())); //return false; } KisResourceLocator::LocatorError r = KisResourceLocator::instance()->initialize(KoResourcePaths::getApplicationRoot() + "/share/krita"); connect(KisResourceLocator::instance(), SIGNAL(progressMessage(const QString&)), this, SLOT(setSplashScreenLoadingText(const QString&))); if (r != KisResourceLocator::LocatorError::Ok ) { QMessageBox::critical(0, i18nc("@title:window", "Krita: Fatal error"), KisResourceLocator::instance()->errorMessages().join('\n') + i18n("\n\nKrita will quit now.")); //return false; } return true; } void KisApplication::loadPlugins() { // qDebug() << "loadPlugins();"; KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); } void KisApplication::loadGuiPlugins() { // qDebug() << "loadGuiPlugins();"; // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); processEvents(); // qDebug() << "loading tools"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); processEvents(); // qDebug() << "loading dockers"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"), QString::fromLatin1("[X-Krita-Version] == 28")); // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); processEvents(); // qDebug() << "loading exiv2"; KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg(false); #if defined(Q_OS_WIN) #ifdef ENV32BIT if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif QString opengl = cfg.canvasState(); if (opengl == "OPENGL_NOT_TRIED" ) { cfg.setCanvasState("TRY_OPENGL"); } else if (opengl != "OPENGL_SUCCESS" && opengl != "TRY_OPENGL") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doNewImage = args.doNewImage(); const bool doTemplate = args.doTemplate(); const bool exportAs = args.exportAs(); const bool exportSequence = args.exportSequence(); const QString exportFileName = args.exportFileName(); d->batchRun = (exportAs || exportSequence || !exportFileName.isEmpty()); const bool needsMainWindow = (!exportAs && !exportSequence); // only show the mainWindow when no command-line mode option is passed bool showmainWindow = (!exportAs && !exportSequence); // would be !batchRun; const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } - KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); - KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load the plugins loadPlugins(); // Load all resources if (!registerResources()) { return false; } // Load the gui plugins loadGuiPlugins(); KisPart *kisPart = KisPart::instance(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); bool sessionNeeded = true; auto sessionMode = cfg.sessionOnStartup(); if (!args.session().isEmpty()) { sessionNeeded = !kisPart->restoreSession(args.session()); } else if (sessionMode == KisConfig::SOS_ShowSessionManager) { showmainWindow = false; sessionNeeded = false; kisPart->showSessionManager(); } else if (sessionMode == KisConfig::SOS_PreviousSession) { KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session"); const QString &sessionName = sessionCfg.readEntry("previousSession"); sessionNeeded = !kisPart->restoreSession(sessionName); } if (sessionNeeded) { kisPart->startBlankSession(); } if (!args.windowLayout().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->windowLayoutServer(); KisWindowLayoutResourceSP windowLayout = rserver->resourceByName(args.windowLayout()); if (windowLayout) { windowLayout->applyLayout(); } } if (showmainWindow) { d->mainWindow = kisPart->currentMainwindow(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace = rserver->resourceByName(args.workspace()); if (workspace) { d->mainWindow->restoreWorkspace(workspace->resourceId()); } } if (args.canvasOnly()) { d->mainWindow->viewManager()->switchCanvasOnly(true); } if (args.fullScreen()) { d->mainWindow->showFullScreen(); } } else { d->mainWindow = kisPart->createMainWindow(); } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test) if (!d->batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.image(); if (doc) { kisPart->addDocument(doc); d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip if (d->batchRun) { continue; } if (createNewDocFromTemplate(fileName, d->mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return false; } KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); bool result = doc->openUrl(QUrl::fromLocalFile(fileName)); if (!result) { errKrita << "Could not load " << fileName << ":" << doc->errorMessage(); QTimer::singleShot(0, this, SLOT(quit())); return false; } if (exportFileName.isEmpty()) { errKrita << "Export destination is not specified for" << fileName << "Please specify export destination with --export-filename option"; QTimer::singleShot(0, this, SLOT(quit())); return false; } qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { errKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } QTimer::singleShot(0, this, SLOT(quit())); return true; } else if (exportSequence) { KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated if (!doc->image()->animationInterface()->hasAnimation()) { errKrita << "This file has no animation." << endl; QTimer::singleShot(0, this, SLOT(quit())); return false; } doc->setFileBatchMode(true); int sequenceStart = 0; KisAsyncAnimationFramesSaveDialog exporter(doc->image(), doc->image()->animationInterface()->fullClipRange(), exportFileName, sequenceStart, 0); exporter.setBatchMode(d->batchRun); KisAsyncAnimationFramesSaveDialog::Result result = exporter.regenerateRange(0); if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) { errKrita << i18n("Failed to render animation frames!") << endl; } QTimer::singleShot(0, this, SLOT(quit())); return true; } else if (d->mainWindow) { if (fileName.endsWith(".bundle")) { d->mainWindow->installBundle(fileName); } else { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { // Normal case, success numberOfOpenDocuments++; } } } } } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash if (d->splashScreen) { d->splashScreen->displayLinks(true); d->splashScreen->displayRecentFiles(true); } Q_FOREACH(const QByteArray &message, d->earlyRemoteArguments) { executeRemoteArguments(message, d->mainWindow); } KisUsageLogger::writeSysInfo(KisUsageLogger::screenInformation()); // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { KisResourceCacheDb::deleteTemporaryResources(); } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(const QString &textToLoad) { if (d->splashScreen) { d->splashScreen->setLoadingText(textToLoad); d->splashScreen->repaint(); } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } void KisApplication::executeRemoteArguments(QByteArray message, KisMainWindow *mainWindow) { KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { createNewDocFromTemplate(filename, mainWindow); } else if (QFile(filename).exists()) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mainWindow->openDocument(QUrl::fromLocalFile(filename), flags); } } } } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw && KisPart::instance()->mainWindows().size() > 0) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { d->earlyRemoteArguments << message; return; } executeRemoteArguments(message, mw); } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mainWindow->openDocument(QUrl::fromLocalFile(url), flags); } } void KisApplication::checkAutosaveFiles() { if (d->batchRun) return; #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // Check for autosave files from a previous run. There can be several, and // we want to offer a restore for every one. Including a nice thumbnail! // Hidden autosave files QStringList filters = QStringList() << QString(".krita-*-*-autosave.kra"); // all autosave files for our application QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Visible autosave files filters = QStringList() << QString("krita-*-*-autosave.kra"); autosaveFiles += dir.entryList(filters, QDir::Files); // Allow the user to make their selection if (autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } d->autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); QDialog::DialogCode result = (QDialog::DialogCode) d->autosaveDialog->exec(); if (result == QDialog::Accepted) { QStringList filesToRecover = d->autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { KisUsageLogger::log(QString("Removing autosave file %1").arg(dir.absolutePath() + "/" + autosaveFile)); QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } autosaveFiles = filesToRecover; } else { autosaveFiles.clear(); } if (autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } if (d->mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; d->mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); } } } // cleanup delete d->autosaveDialog; d->autosaveDialog = nullptr; } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); if (templateURL.scheme().isEmpty()) { templateURL.setScheme("file"); } KisMainWindow::OpenFlags batchFlags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) { dbgUI << "Template loaded..."; return true; } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Template %1 failed to load.", templateURL.toDisplayString())); } } return false; } void KisApplication::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 222ada5dd9..5c157f99b1 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,2300 +1,2324 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 "kis_layer_utils.h" #include "kis_selection_mask.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include "KisReferenceImagesLayer.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" #include "KisCloneDocumentStroke.h" #include #include #include "kis_simple_stroke_strategy.h" // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *_q) : q(_q) , docInfo(new KoDocumentInfo(_q)) // deleted by QObject , importExportManager(new KisImportExportManager(_q)) // deleted manually , autoSaveTimer(new QTimer(_q)) , undoStack(new UndoStack(_q)) // deleted by QObject , m_bAutoDetectedMime(false) , modified(false) , readwrite(true) , firstMod(QDateTime::currentDateTime()) , lastMod(firstMod) , nserver(new KisNameServer(1)) , imageIdleWatcher(2000 /*ms*/) , globalAssistantsColor(KisConfig(true).defaultAssistantsColor()) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *_q) : q(_q) , docInfo(new KoDocumentInfo(*rhs.docInfo, _q)) , importExportManager(new KisImportExportManager(_q)) , autoSaveTimer(new QTimer(_q)) , undoStack(new UndoStack(_q)) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , savingLock(&savingMutex) { copyFromImpl(rhs, _q, CONSTRUCT); } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KisDocument *q = 0; KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; KisMirrorAxisConfig mirrorAxisConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; QColor globalAssistantsColor; KisSharedPtr referenceImagesLayer; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; QString documentStorageID {QUuid::createUuid().toString()}; - KisResourceStorageSP documentResourceStorage {new KisResourceStorage(documentStorageID)}; + KisResourceStorageSP documentResourceStorage; void syncDecorationsWrapperLayerState(); void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } void copyFrom(const Private &rhs, KisDocument *q); void copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy); /// clones the palette list oldList /// the ownership of the returned KoColorSet * belongs to the caller class StrippedSafeSavingLocker; }; void KisDocument::Private::syncDecorationsWrapperLayerState() { if (!this->image) return; KisImageSP image = this->image; KisDecorationsWrapperLayerSP decorationsLayer = KisLayerUtils::findNodeByType(image->root()); const bool needsDecorationsWrapper = gridConfig.showGrid() || (guidesConfig.showGuides() && guidesConfig.hasGuides()) || !assistants.isEmpty(); struct SyncDecorationsWrapperStroke : public KisSimpleStrokeStrategy { SyncDecorationsWrapperStroke(KisDocument *document, bool needsDecorationsWrapper) : KisSimpleStrokeStrategy(QLatin1String("sync-decorations-wrapper"), kundo2_noi18n("start-isolated-mode")), m_document(document), m_needsDecorationsWrapper(needsDecorationsWrapper) { this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { KisDecorationsWrapperLayerSP decorationsLayer = KisLayerUtils::findNodeByType(m_document->image()->root()); if (m_needsDecorationsWrapper && !decorationsLayer) { m_document->image()->addNode(new KisDecorationsWrapperLayer(m_document)); } else if (!m_needsDecorationsWrapper && decorationsLayer) { m_document->image()->removeNode(decorationsLayer); } } private: KisDocument *m_document = 0; bool m_needsDecorationsWrapper = false; }; KisStrokeId id = image->startStroke(new SyncDecorationsWrapperStroke(q, needsDecorationsWrapper)); image->endStroke(id); } void KisDocument::Private::copyFrom(const Private &rhs, KisDocument *q) { copyFromImpl(rhs, q, KisDocument::REPLACE); } void KisDocument::Private::copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy) { if (policy == REPLACE) { delete docInfo; } docInfo = (new KoDocumentInfo(*rhs.docInfo, q)); unit = rhs.unit; mimeType = rhs.mimeType; outputMimeType = rhs.outputMimeType; if (policy == REPLACE) { q->setGuidesConfig(rhs.guidesConfig); q->setMirrorAxisConfig(rhs.mirrorAxisConfig); q->setModified(rhs.modified); q->setAssistants(KisPaintingAssistant::cloneAssistantList(rhs.assistants)); q->setGridConfig(rhs.gridConfig); } else { // in CONSTRUCT mode, we cannot use the functions of KisDocument // because KisDocument does not yet have a pointer to us. guidesConfig = rhs.guidesConfig; mirrorAxisConfig = rhs.mirrorAxisConfig; modified = rhs.modified; assistants = KisPaintingAssistant::cloneAssistantList(rhs.assistants); gridConfig = rhs.gridConfig; } m_bAutoDetectedMime = rhs.m_bAutoDetectedMime; m_url = rhs.m_url; m_file = rhs.m_file; readwrite = rhs.readwrite; firstMod = rhs.firstMod; lastMod = rhs.lastMod; // XXX: the display properties will be shared between different snapshots globalAssistantsColor = rhs.globalAssistantsColor; batchMode = rhs.batchMode; // CHECK THIS! This is what happened to the palette list -- but is it correct here as well? Ask Dmitry!!! // if (policy == REPLACE) { // QList newPaletteList = clonePaletteList(rhs.paletteList); // q->setPaletteList(newPaletteList, /* emitSignal = */ true); // // we still do not own palettes if we did not // } else { // paletteList = rhs.paletteList; // } - if (policy == REPLACE) { - // Clone the resources, but don't add them to the database, only the editable - // version of the document should have those resources in the database. - documentResourceStorage = rhs.documentResourceStorage->clone(); - } - else { - documentResourceStorage = rhs.documentResourceStorage; + + if (rhs.documentResourceStorage) { + if (policy == REPLACE) { + // Clone the resources, but don't add them to the database, only the editable + // version of the document should have those resources in the database. + documentResourceStorage = rhs.documentResourceStorage->clone(); + } + else { + documentResourceStorage = rhs.documentResourceStorage; + } } } class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; -KisDocument::KisDocument() +KisDocument::KisDocument(bool addStorage) : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); - KisResourceLocator::instance()->addStorage(d->documentStorageID, d->documentResourceStorage); + + if (addStorage) { + d->documentResourceStorage.reset(new KisResourceStorage(d->documentStorageID)); + KisResourceLocator::instance()->addStorage(d->documentStorageID, d->documentResourceStorage); + } // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver); d->koShapeController = new KoShapeController(0, d->shapeController); d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { copyFromDocumentImpl(rhs, CONSTRUCT); } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer->disconnect(this); d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } if (KisResourceLocator::instance()->hasStorage(d->documentStorageID)) { KisResourceLocator::instance()->removeStorage(d->documentStorageID); } delete d; } QString KisDocument::uniqueID() const { return d->documentStorageID; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { slotCompleteSavingDocument(job, ImportExportCodes::NoAccessToWrite, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); //return ImportExportCodes::NoAccessToWrite; return false; } KisConfig cfg(true); if (cfg.backupFile() && filePathInfo.exists()) { QString backupDir; switch(cfg.readEntry("backupfilelocation", 0)) { case 1: backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); break; case 2: backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); break; default: // Do nothing: the empty string is user file location break; } int numOfBackupsKept = cfg.readEntry("numberofbackupfiles", 1); QString suffix = cfg.readEntry("backupfilesuffix", "~"); if (numOfBackupsKept == 1) { if (!KBackup::simpleBackupFile(job.filePath, backupDir, suffix)) { qWarning() << "Failed to create simple backup file!" << job.filePath << backupDir << suffix; KisUsageLogger::log(QString("Failed to create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); return false; } else { KisUsageLogger::log(QString("Create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); } } else if (numOfBackupsKept > 2) { if (!KBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept)) { qWarning() << "Failed to create numbered backup file!" << job.filePath << backupDir << suffix; KisUsageLogger::log(QString("Failed to create a numbered backup for %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); return false; } else { KisUsageLogger::log(QString("Create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir)); } } } //KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); if (job.mimeType.isEmpty()) { KisImportExportErrorCode error = ImportExportCodes::FileFormatIncorrect; slotCompleteSavingDocument(job, error, error.errorMessage()); return false; } const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode ,QString)), job, exportConfiguration); if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8") .arg(url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")); return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &_url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, %7 framerate. Export configuration: %8") .arg(_url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration") .arg(url().toLocalFile())); return exportDocumentImpl(ExportFileJob(_url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; if (!filter->convert(this, &buffer).isOk()) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { if (status.isCancelled()) return; const QString fileName = QFileInfo(job.filePath).fileName(); if (!status.isOk()) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage))); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { const QString existingAutoSaveBaseName = localFilePath(); const bool wasRecovered = isRecovered(); setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { /** * If undo stack is already clean/empty, it doesn't emit any * signals, so we might forget update document modified state * (which was set, e.g. while recovering an autosave file) */ if (d->undoStack->isClean()) { setModified(false); } else { d->undoStack->setClean(); } } setRecovered(false); removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->batchMode; } void KisDocument::setFileBatchMode(const bool batchMode) { d->batchMode = batchMode; } KisDocument* KisDocument::lockAndCloneForSaving() { // force update of all the asynchronous nodes before cloning QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root()); KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { if (!window->viewManager()->blockUntilOperationsFinished(d->image)) { return 0; } } } Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } KisDocument *KisDocument::lockAndCreateSnapshot() { KisDocument *doc = lockAndCloneForSaving(); if (doc) { // clone the local resource storage and its contents -- that is, the old palette list - doc->d->documentResourceStorage = doc->d->documentResourceStorage->clone(); + if (doc->d->documentResourceStorage) { + doc->d->documentResourceStorage = doc->d->documentResourceStorage->clone(); + } } return doc; } void KisDocument::copyFromDocument(const KisDocument &rhs) { copyFromDocumentImpl(rhs, REPLACE); } void KisDocument::copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy) { if (policy == REPLACE) { d->copyFrom(*(rhs.d), this); d->undoStack->clear(); } else { // in CONSTRUCT mode, d should be already initialized connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); d->shapeController = new KisShapeController(this, d->nserver); d->koShapeController = new KoShapeController(0, d->shapeController); d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); } setObjectName(rhs.objectName()); slotConfigChanged(); if (rhs.d->image) { if (policy == REPLACE) { d->image->barrierLock(/* readOnly = */ false); rhs.d->image->barrierLock(/* readOnly = */ true); d->image->copyFromImage(*(rhs.d->image)); d->image->unlock(); rhs.d->image->unlock(); setCurrentImage(d->image, /* forceInitialUpdate = */ true); } else { // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false); } } if (rhs.d->preActivatedNode) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(d->image->root(), [&linearizedNodes, &rhs, this](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); if (rhs.d->preActivatedNode.data() == refNode.data()) { d->preActivatedNode = node; } }); } // reinitialize references' signal connection KisReferenceImagesLayerSP referencesLayer = this->referenceImagesLayer(); setReferenceImagesLayer(referencesLayer, false); KisDecorationsWrapperLayerSP decorationsLayer = KisLayerUtils::findNodeByType(d->image->root()); if (decorationsLayer) { decorationsLayer->setDocument(this); } if (policy == REPLACE) { setModified(true); } } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { { /** * The caller guarantees that no one else uses the document (usually, * it is a temporary document created specifically for exporting), so * we don't need to copy or lock the document. Instead we should just * ensure the barrier lock is synced and then released. */ Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportErrorCode status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status.isOk(); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { return initiateSavingInBackground(actionName, receiverObject, receiverMethod, job, exportConfiguration, std::unique_ptr()); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument; if (!optionalClonedDocument) { clonedDocument.reset(lockAndCloneForSaving()); } else { clonedDocument.reset(optionalClonedDocument.release()); } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } auto waitForImage = [] (KisImageSP image) { KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { window->viewManager()->blockUntilOperationsFinishedForced(image); } } }; { KisNodeSP newRoot = clonedDocument->image()->root(); KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) { KisLayerUtils::forceAllDelayedNodesUpdate(newRoot); waitForImage(clonedDocument->image()); } } if (clonedDocument->image()->hasOverlaySelectionMask()) { clonedDocument->image()->setOverlaySelectionMask(0); waitForImage(clonedDocument->image()); } KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) { waitForImage(clonedDocument->image()); } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportErrorCode, QString)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportErrorCode, QString))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { // the state should have been deinitialized in slotChildCompletedSavingInBackground() KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } } return started; } void KisDocument::slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage) { KIS_ASSERT_RECOVER_RETURN(isSaving()); KIS_ASSERT_RECOVER(d->backgroundSaveDocument) { d->savingMutex.unlock(); return; } if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); KIS_ASSERT_RECOVER(d->backgroundSaveJob.isValid()) { d->savingMutex.unlock(); return; } const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); // unlock at the very end d->savingMutex.unlock(); QFileInfo fi(job.filePath); KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3. Size: %4. MD5 Hash: %5") .arg(job.filePath) .arg(QString::fromLatin1(job.mimeType)) .arg(!status.isOk() ? exportErrorToUserMessage(status, errorMessage) : "OK") .arg(fi.size()) .arg(QString::fromLatin1(KoMD5Generator().generateHash(job.filePath).toHex()))); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); KisUsageLogger::log(QString("Autosaving: %1").arg(autoSaveFileName)); const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0, std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), Qt::BlockingQueuedConnection); KisStrokeId strokeId = d->image->startStroke(stroke); d->image->endStroke(strokeId); setInfiniteAutoSaveInterval(); } else if (!started) { setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotAutoSave() { slotAutoSaveImpl(std::unique_ptr()); } void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) { slotAutoSaveImpl(std::unique_ptr(clonedDocument)); } void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (!status.isOk()) { setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg(true); d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer->stop(); // until the next change d->autoSaveFailureCount = 0; } else { setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } KisImportExportErrorCode initializationStatus(ImportExportCodes::OK); d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); if (!initializationStatus.isOk()) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); emit sigBackgroundSavingFinished(initializationStatus, initializationStatus.errorMessage()); return false; } typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { emit sigBackgroundSavingFinished(ImportExportCodes::InternalError, ""); return; } KisImportExportErrorCode status = d->childSavingFuture.result(); const QString errorMessage = status.errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { if (isReadWrite() && delay > 0) { d->autoSaveTimer->start(delay * 1000); } else { d->autoSaveTimer->stop(); } } void KisDocument::setNormalAutoSaveInterval() { setAutoSaveDelay(d->autoSaveDelay); d->autoSaveFailureCount = 0; } void KisDocument::setEmergencyAutoSaveInterval() { const int emergencyAutoSaveInterval = 10; /* sec */ setAutoSaveDelay(emergencyAutoSaveInterval); d->autoSaveFailureCount++; } void KisDocument::setInfiniteAutoSaveInterval() { setAutoSaveDelay(-1); } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QString prefix = KisConfig(true).readEntry("autosavefileshidden") ? QString(".") : QString(); QRegularExpression autosavePattern1("^\\..+-autosave.kra$"); QRegularExpression autosavePattern2("^.+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) { // 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%7%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #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%7%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #endif } else { retval = QString("%1%2%5%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension).arg(prefix); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //qDebug() <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf)); QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (ret) { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

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

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath()) && !fileBatchMode()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; KisMainWindow *window = KisPart::instance()->currentMainwindow(); KoUpdaterPtr updater; if (window && window->viewManager()) { updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName); if (!status.isOk()) { if (window && window->viewManager()) { updater->cancel(); } QString msg = status.errorMessage(); if (!msg.isEmpty() && !fileBatchMode()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty() && !fileBatchMode()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); d->syncDecorationsWrapperLayerState(); emit sigLoadingFinished(); undoStack()->clear(); return true; } void KisDocument::autoSaveOnPause() { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); QUrl url("file:/" + autoSaveFileName); bool started = exportDocumentSync(url, nativeFormatMimeType()); if (started) { d->modifiedAfterAutosave = false; dbgAndroid << "autoSaveOnPause successful"; } else { qWarning() << "Could not auto-save when paused"; } } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; const QString _url(url().fileName()); // if URL is empty...it is probably an unsaved file if (_url.isEmpty()) { c = " [" + i18n("Not Saved") + "] "; } else { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered) { // Eliminate any auto-save file QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir if (QFile::exists(asf)) { KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf)); QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME if (QFile::exists(asf)) { KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf)); QFile::remove(asf); } QList expressions; expressions << QRegularExpression("^\\..+-autosave.kra$") << QRegularExpression("^.+-autosave.kra$"); Q_FOREACH(const QRegularExpression &rex, expressions) { if (wasRecovered && !autosaveBaseName.isEmpty() && rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() && QFile::exists(autosaveBaseName)) { KisUsageLogger::log(QString("Removing autosave file: %1").arg(autosaveBaseName)); QFile::remove(autosaveBaseName); } } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg(true); if (d->undoStack->undoLimit() != cfg.undoStackLimit()) { if (!d->undoStack->isClean()) { d->undoStack->clear(); } d->undoStack->setUndoLimit(cfg.undoStackLimit()); } d->autoSaveDelay = cfg.autoSaveInterval(); setNormalAutoSaveInterval(); } void KisDocument::slotImageRootChanged() { d->syncDecorationsWrapperLayerState(); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { if (d->gridConfig != config) { d->gridConfig = config; d->syncDecorationsWrapperLayerState(); emit sigGridConfigChanged(config); } } QList KisDocument::paletteList() { + qDebug() << "PALETTELIST storage" << d->documentResourceStorage; + QList _paletteList; + if (d->documentResourceStorage.isNull()) { + qWarning() << "No documentstorage for palettes"; + return _paletteList; + } + QSharedPointer iter = d->documentResourceStorage->resources(ResourceType::Palettes); while (iter->hasNext()) { iter->next(); KoResourceSP resource = iter->resource(); if (resource && resource->valid()) { _paletteList << resource.dynamicCast(); } } return _paletteList; } void KisDocument::setPaletteList(const QList &paletteList, bool emitSignal) { + qDebug() << "SET PALETTE LIST" << paletteList.size() << "storage" << d->documentResourceStorage; + QList oldPaletteList; - QSharedPointer iter = d->documentResourceStorage->resources(ResourceType::Palettes); - while (iter->hasNext()) { - iter->next(); - KoResourceSP resource = iter->resource(); - if (resource && resource->valid()) { - oldPaletteList << resource.dynamicCast(); - } - } - if (oldPaletteList != paletteList) { - KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(ResourceType::Palettes); - Q_FOREACH(KoColorSetSP palette, oldPaletteList) { - resourceModel->removeResource(palette); - } - Q_FOREACH(KoColorSetSP palette, paletteList) { - resourceModel->addResource(palette, d->documentStorageID); + if (d->documentResourceStorage) { + QSharedPointer iter = d->documentResourceStorage->resources(ResourceType::Palettes); + while (iter->hasNext()) { + iter->next(); + KoResourceSP resource = iter->resource(); + if (resource && resource->valid()) { + oldPaletteList << resource.dynamicCast(); + } } - if (emitSignal) { - emit sigPaletteListChanged(oldPaletteList, paletteList); + if (oldPaletteList != paletteList) { + KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(ResourceType::Palettes); + Q_FOREACH(KoColorSetSP palette, oldPaletteList) { + resourceModel->removeResource(palette); + } + Q_FOREACH(KoColorSetSP palette, paletteList) { + qDebug()<< "loading palette into document" << palette->filename(); + resourceModel->addResource(palette, d->documentStorageID); + } + if (emitSignal) { + emit sigPaletteListChanged(oldPaletteList, paletteList); + } } } } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; d->syncDecorationsWrapperLayerState(); emit sigGuidesConfigChanged(d->guidesConfig); } const KisMirrorAxisConfig& KisDocument::mirrorAxisConfig() const { return d->mirrorAxisConfig; } void KisDocument::setMirrorAxisConfig(const KisMirrorAxisConfig &config) { if (d->mirrorAxisConfig == config) { return; } d->mirrorAxisConfig = config; setModified(true); emit sigMirrorAxisConfigChanged(); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) { return false; } if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) { return false; } d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisImageSP image; 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()); image->waitForDone(); documentInfo()->setAboutInfo("title", name); documentInfo()->setAboutInfo("abstract", description); KisLayerSP layer; if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) { KoColor strippedAlpha = bgColor; strippedAlpha.setOpacity(OPACITY_OPAQUE_U8); if (bgStyle == KisConfig::RASTER_LAYER) { layer = new KisPaintLayer(image.data(), "Background", OPACITY_OPAQUE_U8, cs);; layer->paintDevice()->setDefaultPixel(strippedAlpha); } else if (bgStyle == KisConfig::FILL_LAYER) { KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration(); filter_config->setProperty("color", strippedAlpha.toQColor()); layer = new KisGeneratorLayer(image.data(), "Background Fill", filter_config, image->globalSelection()); } layer->setOpacity(bgColor.opacityU8()); if (numberOfLayers > 1) { //Lock bg layer if others are present. layer->setUserLocked(true); } } else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer). image->setDefaultProjectionColor(bgColor); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); } Q_CHECK_PTR(layer); image->addNode(layer.data(), image->rootLayer().data()); layer->setDirty(QRect(0, 0, width, height)); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } KisConfig cfg(false); cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); KisUsageLogger::log(QString("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8") .arg(name) .arg(width).arg(height) .arg(imageResolution * 72.0) .arg(image->colorSpace()->colorModelId().name()) .arg(image->colorSpace()->colorDepthId().name()) .arg(image->colorSpace()->profile()->name()) .arg(numberOfLayers)); QApplication::restoreOverrideCursor(); return true; } bool KisDocument::isSaving() const { const bool result = d->savingMutex.tryLock(); if (result) { d->savingMutex.unlock(); } return !result; } void KisDocument::waitForSavingToComplete() { if (isSaving()) { KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); f.waitForMutex(&d->savingMutex); } } KoShapeControllerBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList &value) { if (d->assistants != value) { d->assistants = value; d->syncDecorationsWrapperLayerState(); emit sigAssistantsChanged(); } } KisReferenceImagesLayerSP KisDocument::referenceImagesLayer() const { if (!d->image) return KisReferenceImagesLayerSP(); KisReferenceImagesLayerSP referencesLayer = KisLayerUtils::findNodeByType(d->image->root()); return referencesLayer; } void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage) { KisReferenceImagesLayerSP currentReferenceLayer = referenceImagesLayer(); if (currentReferenceLayer == layer) { return; } if (currentReferenceLayer) { currentReferenceLayer->disconnect(this); } if (updateImage) { if (currentReferenceLayer) { d->image->removeNode(currentReferenceLayer); } if (layer) { d->image->addNode(layer); } } currentReferenceLayer = layer; if (currentReferenceLayer) { connect(currentReferenceLayer, SIGNAL(sigUpdateCanvas(QRectF)), this, SIGNAL(sigReferenceImagesChanged())); } emit sigReferenceImagesLayerChanged(layer); } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate) { if (d->image) { // Disconnect existing sig/slot connections d->image->setUndoStore(new KisDumbUndoStore()); d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; + if (d->documentResourceStorage){ + d->documentResourceStorage->setMetaData(KisResourceStorage::s_meta_name, image->objectName()); + } + d->setImageAndInitIdleWatcher(image); d->image->setUndoStore(new KisDocumentUndoStore(this)); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); connect(d->image, SIGNAL(sigLayersChangedAsync()), this, SLOT(slotImageRootChanged())); if (forceInitialUpdate) { d->image->initialRefreshGraph(); } } void KisDocument::hackPreliminarySetImage(KisImageSP image) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image); // we set image without connecting idle-watcher, because loading // hasn't been finished yet d->image = image; d->shapeController->setImage(image); } void KisDocument::setImageModified() { // we only set as modified if undo stack is not at clean state setModified(!d->undoStack->isClean()); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage) { return errorMessage.isEmpty() ? status.errorMessage() : errorMessage; } void KisDocument::setAssistantsGlobalColor(QColor color) { d->globalAssistantsColor = color; } QColor KisDocument::assistantsGlobalColor() { return d->globalAssistantsColor; } QRectF KisDocument::documentBounds() const { QRectF bounds = d->image->bounds(); KisReferenceImagesLayerSP referenceImagesLayer = this->referenceImagesLayer(); if (referenceImagesLayer) { bounds |= referenceImagesLayer->boundingImageRect(); } return bounds; } diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h index 5d71937a8d..cf24b4bf65 100644 --- a/libs/ui/KisDocument.h +++ b/libs/ui/KisDocument.h @@ -1,715 +1,715 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISDOCUMENT_H #define KISDOCUMENT_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kritaui_export.h" #include class QString; class KUndo2Command; class KoUnit; class KoColor; class KoColorSpace; class KoShapeControllerBase; class KoShapeLayer; class KoStore; class KoOdfReadStore; class KoDocumentInfo; class KoDocumentInfoDlg; class KisImportExportManager; class KisUndoStore; class KisPart; class KisGridConfig; class KisGuidesConfig; class KisMirrorAxisConfig; class QDomDocument; class KisReferenceImagesLayer; #define KIS_MIME_TYPE "application/x-krita" /** * The %Calligra document class * * This class provides some functionality each %Calligra document should have. * * @short The %Calligra document class */ class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase { Q_OBJECT protected: - explicit KisDocument(); + explicit KisDocument(bool addStorage = true); /** * @brief KisDocument makes a deep copy of the document \p rhs. * The caller *must* ensure that the image is properly * locked and is in consistent state before asking for * cloning. * @param rhs the source document to copy from */ explicit KisDocument(const KisDocument &rhs); public: enum OpenFlag { None = 0, DontAddToRecent = 0x1, RecoveryFile = 0x2 }; Q_DECLARE_FLAGS(OpenFlags, OpenFlag) /** * Destructor. * * The destructor does not delete any attached KisView objects and it does not * delete the attached widget as returned by widget(). */ ~KisDocument() override; /** * @brief uniqueID is a temporary unique ID that identifies the document. It is * generated on creation and can be used to uniquely associated temporary objects * with this document. * * @return the temporary unique id for this document. */ QString uniqueID() const; /** * @brief reload Reloads the document from the original url * @return the result of loading the document */ bool reload(); /** * @brief creates a clone of the document and returns it. Please make sure that you * hold all the necessary locks on the image before asking for a clone! */ KisDocument* clone(); /** * @brief openUrl Open an URL * @param url The URL to open * @param flags Control specific behavior * @return success status */ bool openUrl(const QUrl &url, OpenFlags flags = None); /** * Opens the document given by @p url, without storing the URL * in the KisDocument. * Call this instead of openUrl() to implement KisMainWindow's * File --> Import feature. * * @note This will call openUrl(). To differentiate this from an ordinary * Open operation (in any reimplementation of openUrl() or openFile()) * call isImporting(). */ bool importDocument(const QUrl &url); /** * Saves the document as @p url without changing the state of the * KisDocument (URL, modified flag etc.). Call this instead of * KisParts::ReadWritePart::saveAs() to implement KisMainWindow's * File --> Export feature. */ bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0); /** * Exports he document is a synchronous way. The caller must ensure that the * image is not accessed by any other actors, because the exporting happens * without holding the image lock. */ bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0); private: bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); public: /** * @brief Sets whether the document can be edited or is read only. * * This recursively applied to all child documents and * KisView::updateReadWrite is called for every attached * view. */ void setReadWrite(bool readwrite = true); /** * To be preferred when a document exists. It is fast when calling * it multiple times since it caches the result that readNativeFormatMimeType() * delivers. * This comes from the X-KDE-NativeMimeType key in the .desktop file. */ static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; } /// Checks whether a given mimetype can be handled natively. bool isNativeFormat(const QByteArray& mimetype) const; /// Returns a list of the mimetypes considered "native", i.e. which can /// be saved by KisDocument without a filter, in *addition* to the main one static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; } /** * Returns the actual mimetype of the document */ QByteArray mimeType() const override; /** * @brief Sets the mime type for the document. * * When choosing "save as" this is also the mime type * selected by default. */ void setMimeType(const QByteArray & mimeType) override; /** * @return true if file operations should inhibit the option dialog */ bool fileBatchMode() const; /** * @param batchMode if true, do not show the option dialog for file operations. */ void setFileBatchMode(const bool batchMode); /** * Sets the error message to be shown to the user (use i18n()!) * when loading or saving fails. * If you asked the user about something and they chose "Cancel", */ void setErrorMessage(const QString& errMsg); /** * Return the last error message. Usually KisDocument takes care of * showing it; this method is mostly provided for non-interactive use. */ QString errorMessage() const; /** * Sets the warning message to be shown to the user (use i18n()!) * when loading or saving fails. */ void setWarningMessage(const QString& warningMsg); /** * Return the last warning message set by loading or saving. Warnings * mean that the document could not be completely loaded, but the errors * were not absolutely fatal. */ QString warningMessage() const; /** * @brief Generates a preview picture of the document * @note The preview is used in the File Dialog and also to create the Thumbnail */ QPixmap generatePreview(const QSize& size); /** * Tells the document that its title has been modified, either because * the modified status changes (this is done by setModified() ) or * because the URL or the document-info's title changed. */ void setTitleModified(); /** * @brief Sets the document to empty. * * Used after loading a template * (which is not empty, but not the user's input). * * @see isEmpty() */ void setEmpty(bool empty = true); /** * Return a correctly created QDomDocument for this KisDocument, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * @param tagName the name of the tag for the root element * @param version the DTD version (usually the application's version). */ QDomDocument createDomDocument(const QString& tagName, const QString& version) const; /** * Return a correctly created QDomDocument for an old (1.3-style) %Calligra document, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * This static method can be used e.g. by filters. * @param appName the app's instance name, e.g. words, kspread, kpresenter etc. * @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter. * @param version the DTD version (usually the application's version). */ static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version); /** * Loads a document in the native format from a given URL. * Reimplement if your native format isn't XML. * * @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter */ bool loadNativeFormat(const QString & file); /** * Set standard autosave interval that is set by a config file */ void setNormalAutoSaveInterval(); /** * Set emergency interval that autosave uses when the image is busy, * by default it is 10 sec */ void setEmergencyAutoSaveInterval(); /** * Disable autosave */ void setInfiniteAutoSaveInterval(); /** * @return the information concerning this document. * @see KoDocumentInfo */ KoDocumentInfo *documentInfo() const; /** * Performs a cleanup of unneeded backup files */ void removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered); /** * Returns true if this document or any of its internal child documents are modified. */ bool isModified() const override; /** * @return caption of the document * * Caption is of the form "[title] - [url]", * built out of the document info (title) and pretty-printed * document URL. * If the title is not present, only the URL it returned. */ QString caption() const; /** * Sets the document URL to empty URL * KParts doesn't allow this, but %Calligra apps have e.g. templates * After using loadNativeFormat on a template, one wants * to set the url to QUrl() */ void resetURL(); /** * @internal (public for KisMainWindow) */ void setMimeTypeAfterLoading(const QString& mimeType); /** * Returns the unit used to display all measures/distances. */ KoUnit unit() const; /** * Sets the unit used to display all measures/distances. */ void setUnit(const KoUnit &unit); KisGridConfig gridConfig() const; void setGridConfig(const KisGridConfig &config); /// returns the guides data for this document. const KisGuidesConfig& guidesConfig() const; void setGuidesConfig(const KisGuidesConfig &data); /** * @brief paletteList returns all the palettes found in the document's local resource storage */ QList paletteList(); /** * @brief setPaletteList replaces the palettes in the document's local resource storage with the list * of palettes passed to this function. It will then emitsigPaletteListChanged with both the old and * the new list, if emitsignal is true. */ void setPaletteList(const QList &paletteList, bool emitSignal = false); const KisMirrorAxisConfig& mirrorAxisConfig() const; void setMirrorAxisConfig(const KisMirrorAxisConfig& config); void clearUndoHistory(); /** * Sets the modified flag on the document. This means that it has * to be saved or not before deleting it. */ void setModified(bool _mod); void setRecovered(bool value); bool isRecovered() const; void updateEditingTime(bool forceStoreElapsed); /** * Returns the global undo stack */ KUndo2Stack *undoStack(); /** * @brief importExportManager gives access to the internal import/export manager * @return the document's import/export manager */ KisImportExportManager *importExportManager() const; /** * @brief serializeToNativeByteArray daves the document into a .kra file wtitten * to a memory-based byte-array * @return a byte array containing the .kra file */ QByteArray serializeToNativeByteArray(); /** * @brief isInSaving shown if the document has any (background) saving process or not * @return true if there is some saving in action */ bool isInSaving() const; public Q_SLOTS: /** * Adds a command to the undo stack and executes it by calling the redo() function. * @param command command to add to the undo stack */ void addCommand(KUndo2Command *command); /** * Begins recording of a macro command. At the end endMacro needs to be called. * @param text command description */ void beginMacro(const KUndo2MagicString &text); /** * Ends the recording of a macro command. */ void endMacro(); Q_SIGNALS: /** * This signal is emitted when the unit is changed by setUnit(). * It is common to connect views to it, in order to change the displayed units * (e.g. in the rulers) */ void unitChanged(const KoUnit &unit); /** * Emitted e.g. at the beginning of a save operation * This is emitted by KisDocument and used by KisView to display a statusbar message */ void statusBarMessage(const QString& text, int timeout = 0); /** * Emitted e.g. at the end of a save operation * This is emitted by KisDocument and used by KisView to clear the statusbar message */ void clearStatusBarMessage(); /** * Emitted when the document is modified */ void modified(bool); void titleModified(const QString &caption, bool isModified); void sigLoadingFinished(); void sigSavingFinished(); void sigGuidesConfigChanged(const KisGuidesConfig &config); void sigBackgroundSavingFinished(KisImportExportErrorCode status, const QString &errorMessage); void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage); void sigReferenceImagesChanged(); void sigMirrorAxisConfigChanged(); void sigGridConfigChanged(const KisGridConfig &config); void sigReferenceImagesLayerChanged(KisSharedPtr layer); /** * Emitted when the palette list has changed. * The pointers in oldPaletteList are to be deleted by the resource server. **/ void sigPaletteListChanged(const QList &oldPaletteList, const QList &newPaletteList); void sigAssistantsChanged(); private Q_SLOTS: void finishExportInBackground(); void slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage); void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage); void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage); void slotInitiateAsyncAutosaving(KisDocument *clonedDocument); private: friend class KisPart; friend class SafeSavingLocker; bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument); bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); bool startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Activate/deactivate/configure the autosave feature. * @param delay in seconds, 0 to disable */ void setAutoSaveDelay(int delay); /** * Generate a name for the document. */ QString newObjectName(); QString generateAutoSaveFileName(const QString & path) const; /** * Loads a document * * Applies a filter if necessary, and calls loadNativeFormat in any case * You should not have to reimplement, except for very special cases. * * NOTE: this method also creates a new KisView instance! * * This method is called from the KReadOnlyPart::openUrl method. */ bool openFile(); public: bool isAutosaving() const override; public: QString localFilePath() const override; void setLocalFilePath( const QString &localFilePath ); KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const; bool isReadWrite() const; QUrl url() const override; void setUrl(const QUrl &url) override; bool closeUrl(bool promptToSave = true); bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0); /** * Create a new image that has this document as a parent and * replace the current image with this image. */ bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &imageDescription, const double imageResolution); bool isSaving() const; void waitForSavingToComplete(); KisImageWSP image() const; /** * @brief savingImage provides a detached, shallow copy of the original image that must be used when saving. * Any strokes in progress will not be applied to this image, so the result might be missing some data. On * the other hand, it won't block. * * @return a shallow copy of the original image, or 0 is saving is not in progress */ KisImageSP savingImage() const; /** * Set the current image to the specified image and turn undo on. */ void setCurrentImage(KisImageSP image, bool forceInitialUpdate = true); /** * Set the image of the document preliminary, before the document * has completed loading. Some of the document items (shapes) may want * to access image properties (bounds and resolution), so we should provide * it to them even before the entire image is loaded. * * Right now, the only use by KoShapeRegistry::createShapeFromOdf(), remove * after it is deprecated. */ void hackPreliminarySetImage(KisImageSP image); KisUndoStore* createUndoStore(); /** * The shape controller matches internal krita image layers with * the flake shape hierarchy. */ KoShapeControllerBase * shapeController() const; KoShapeLayer* shapeForNode(KisNodeSP layer) const; /** * Set the list of nodes that was marked as currently active. Used *only* * for saving loading. Never use it for tools or processing. */ void setPreActivatedNode(KisNodeSP activatedNode); /** * @return the node that was set as active during loading. Used *only* * for saving loading. Never use it for tools or processing. */ KisNodeSP preActivatedNode() const; /// @return the list of assistants associated with this document QList assistants() const; /// @replace the current list of assistants with @param value void setAssistants(const QList &value); void setAssistantsGlobalColor(QColor color); QColor assistantsGlobalColor(); /** * Get existing reference images layer or null if none exists. */ KisSharedPtr referenceImagesLayer() const; void setReferenceImagesLayer(KisSharedPtr layer, bool updateImage); bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Return the bounding box of the image and associated elements (e.g. reference images) */ QRectF documentBounds() const; /** * @brief Start saving when android activity is pushed to the background */ void autoSaveOnPause(); Q_SIGNALS: void completed(); void canceled(const QString &); private Q_SLOTS: void setImageModified(); void slotAutoSave(); void slotUndoStackCleanChanged(bool value); void slotConfigChanged(); void slotImageRootChanged(); /** * @brief try to clone the image. This method handles all the locking for you. If locking * has failed, no cloning happens * @return cloned document on success, null otherwise */ KisDocument *lockAndCloneForSaving(); public: KisDocument *lockAndCreateSnapshot(); void copyFromDocument(const KisDocument &rhs); private: enum CopyPolicy { CONSTRUCT = 0, ///< we are copy-constructing a new KisDocument REPLACE ///< we are replacing the current KisDocument with another }; void copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy); QString exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage); QString prettyPathOrUrl() const; bool openUrlInternal(const QUrl &url); void slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument); class Private; Private *const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags) Q_DECLARE_METATYPE(KisDocument*) #endif diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 2fa5c84b85..ceff49821f 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2851 +1,2851 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_selection_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_ANDROID #include #endif #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "kis_group_layer.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #include #include "KisCanvasWindow.h" #include "kis_action.h" #include class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new KisSignalMapper(parent)) , documentMapper(new KisSignalMapper(parent)) #ifdef Q_OS_ANDROID , fileManager(new KisAndroidFileManager(parent)) #endif { if (id.isNull()) this->id = QUuid::createUuid(); welcomeScroller = new QScrollArea(); welcomeScroller->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setWidget(welcomePage); welcomeScroller->setWidgetResizable(true); widgetStack->addWidget(welcomeScroller); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *toggleDetachCanvas {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KisResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; QScrollArea *welcomeScroller {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; KisSignalMapper *windowMapper; KisSignalMapper *documentMapper; KisCanvasWindow *canvasWindow {0}; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisSignalAutoConnectionsStore screenConnectionsStore; #ifdef Q_OS_ANDROID KisAndroidFileManager *fileManager; #endif KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { d->workspacemodel = KisResourceModelProvider::resourceModel(ResourceType::Workspaces); connect(d->workspacemodel, SIGNAL(afterResourcesLayoutReset()), this, SLOT(updateWindowMenu())); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); 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_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } // Load all the actions from the tool plugins Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { toolFactory->createActions(actionCollection()); } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); d->canvasWindow = new KisCanvasWindow(this); actionCollection()->addAssociatedWidget(d->canvasWindow); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); connect(guiFactory(), SIGNAL(makingChanges(bool)), SLOT(slotXmlGuiMakingChanges(bool))); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); toolBar->setMovable(KisConfig(true).readEntry("LockAllDockerPanels", false)); 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!"; } } KToolBar::setToolBarsLocked(KisConfig(true).readEntry("LockAllDockerPanels", false)); plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); 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)); } if (cfg.readEntry("CanvasOnlyActive", false)) { QString currentWorkspace = cfg.readEntry("CurrentWorkspace", "Default"); KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace = rserver->resourceByName(currentWorkspace); if (workspace) { restoreWorkspace(workspace->resourceId()); } cfg.writeEntry("CanvasOnlyActive", false); menuBar()->setVisible(true); } this->winId(); // Ensures the native window has been created. QWindow *window = this->windowHandle(); connect(window, SIGNAL(screenChanged(QScreen *)), this, SLOT(windowScreenChanged(QScreen *))); } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // qDebug() << "", "").replace("", "") // << "\n\ticonText=" << action->iconText().replace("&", "&") // << "\n\tshortcut=" << action->shortcut().toString() // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "\n\tstatusTip=" << action->statusTip() // << "\n/>\n" ; // } // else { // dbgKrita << "Got a non-qaction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view, QMdiSubWindow *subWindow) { if (d->activeView == view && !subWindow) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view, subWindow); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { /** * If we are the last view of the window, Qt will not activate another tab * before destroying tab/window. In this case we should clear all the dangling * pointers manually by setting the current view to null */ viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView, QMdiSubWindow *subwin) { 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(); if (!subwin) { subwin = d->mdiArea->addSubWindow(imageView); } else { subwin->setWidget(imageView); } imageView->setSubWindow(subwin); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg(true); subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); #ifdef Q_OS_MACOS connect(subwin, SIGNAL(destroyed()), SLOT(updateSubwindowFlags())); updateSubwindowFlags(); #endif if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } /** * 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(); // 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() { QScopedPointer dlgPreferences(new KisDlgPreferences(this)); if (dlgPreferences->editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } updateWindowMenu(); 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); } if (d->mdiArea) { d->mdiArea->setPalette(qApp->palette()); for (int i=0; imdiArea->subWindowList().size(); i++) { QMdiSubWindow *window = d->mdiArea->subWindowList().at(i); if (window) { window->setPalette(qApp->palette()); KisView *view = qobject_cast(window->widget()); if (view) { view->slotThemeChanged(qApp->palette()); } } } } emit themeChanged(); } bool KisMainWindow::canvasDetached() const { return centralWidget() != d->widgetStack; } void KisMainWindow::setCanvasDetached(bool detach) { if (detach == canvasDetached()) return; QWidget *outgoingWidget = centralWidget() ? takeCentralWidget() : nullptr; QWidget *incomingWidget = d->canvasWindow->swapMainWidget(outgoingWidget); if (incomingWidget) { setCentralWidget(incomingWidget); } if (detach) { d->canvasWindow->show(); } else { d->canvasWindow->hide(); } } void KisMainWindow::slotFileSelected(QString path) { QString url = path; if (!url.isEmpty()) { bool res = openDocument(QUrl::fromLocalFile(url), Import); if (!res) { warnKrita << "Loading" << url << "failed"; } } } void KisMainWindow::slotEmptyFilePath() { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The chosen file's location could not be found. Does it exist?")); } QWidget * KisMainWindow::canvasWindow() const { return d->canvasWindow; } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } const QStringList templateDirs = KoResourcePaths::findDirs("templates"); for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the templates directory. break; } } } if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); d->welcomePage->populateRecentDocuments(); } void KisMainWindow::removeRecentUrl(const QUrl &url) { d->recentFiles->removeUrl(url); KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); caption = "RESOURCES REWRITE GOING ON " + caption; if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) { d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); } else { d->saveAction->setToolTip(i18n("Save")); } } } void KisMainWindow::updateCaption(const QString &caption, bool modified) { QString versionString = KritaVersionWrapper::versionString(true); QString title = caption; if (!title.contains(QStringLiteral("[*]"))) { // append the placeholder so that the modified mechanism works title.append(QStringLiteral(" [*]")); } if (d->mdiArea->activeSubWindow()) { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) d->mdiArea->activeSubWindow()->setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else d->mdiArea->activeSubWindow()->setWindowTitle(title); #endif d->mdiArea->activeSubWindow()->setWindowModified(modified); } else { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else setWindowTitle(title); #endif } setWindowModified(modified); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); KisDocument::OpenFlags openFlags = KisDocument::None; // XXX: Why this duplication of of OpenFlags... if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } if (flags & RecoveryFile && ( url.toLocalFile().startsWith(QDir::tempPath()) || url.toLocalFile().startsWith(QDir::homePath())) ) { newdoc->setUrl(QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + "/" + QFileInfo(url.toLocalFile()).fileName())); newdoc->save(false, 0); } return true; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document, QMdiSubWindow *subWindow) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, d->viewManager, this); addView(view, subWindow); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { KisUsageLogger::log(QString("Loading canceled. Error:").arg(errMsg)); if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { KisUsageLogger::log(QString("Saving canceled. Error:").arg(errMsg)); if (!errMsg.isEmpty()) { // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); } slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { KisUsageLogger::log(QString("Saving Completed")); KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } QImage KisMainWindow::layoutThumbnail() { int size = 256; qreal scale = qreal(size)/qreal(qMax(geometry().width(), geometry().height())); QImage layoutThumbnail = QImage(qRound(geometry().width()*scale), qRound(geometry().height()*scale), QImage::Format_ARGB32); QPainter gc(&layoutThumbnail); gc.fillRect(0, 0, layoutThumbnail.width(), layoutThumbnail.height(), this->palette().dark()); for (int childW = 0; childW< children().size(); childW++) { if (children().at(childW)->isWidgetType()) { QWidget *w = dynamic_cast(children().at(childW)); if (w->isVisible()) { QRect wRect = QRectF(w->geometry().x()*scale , w->geometry().y()*scale , w->geometry().width()*scale , w->geometry().height()*scale ).toRect(); wRect = wRect.intersected(layoutThumbnail.rect().adjusted(-1, -1, -1, -1)); gc.setBrush(this->palette().window()); if (w == d->widgetStack) { gc.setBrush(d->mdiArea->background()); } gc.setPen(this->palette().windowText().color()); gc.drawRect(wRect); } } } gc.end(); return layoutThumbnail; } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } if (document->url().isEmpty()) { saveas = true; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty() && !d->lastExportLocation.contains(QDir::tempPath())) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.completeBaseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).completeBaseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).completeBaseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; } } updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { if (hackIsSaving()) { e->setAccepted(false); return; } if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); d->canvasWindow->close(); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragMove(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::dragLeave() { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; item.title = i18n("Custom Document"); startupWidget->addCustomDocumentWidget(item.widget, item.title, "Custom Document", 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, "Create from ClipBoard", item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { #ifndef Q_OS_ANDROID QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } #else Q_UNUSED(isImporting) d->fileManager->openImportFile(); connect(d->fileManager, SIGNAL(sigFileSelected(QString)), this, SLOT(slotFileSelected(QString))); connect(d->fileManager, SIGNAL(sigEmptyFilePath()), this, SLOT(slotEmptyFilePath())); #endif } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceProvider *KisMainWindow::resourceManager() const { return d->viewManager->canvasResourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(int workspaceId) { KisWorkspaceResourceSP workspace = KisResourceModelProvider::resourceModel(ResourceType::Workspaces) ->resourceForId(workspaceId).dynamicCast(); bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { // Do not close while KisMainWindow has the savingEntryMutex locked, bug409395. // After the background saving job is initiated, KisDocument blocks closing // while it saves itself. if (hackIsSaving()) { return; } KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportErrorCode status = importer.import(files, firstFrame, step); if (!status.isOk() && !status.isInternalError()) { QString msg = status.errorMessage(); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (lockAllDockers) { if (dockWidget->titleBarWidget()) { dockWidget->titleBarWidget()->setVisible(false); } dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_MACOS dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } /** * Qt has a weirdness, it has hardcoded shortcuts added to an action * in the window menu. We need to reset the shortcuts for that menu * to nothing, otherwise the shortcuts cannot be made configurable. * * See: https://bugs.kde.org/show_bug.cgi?id=352205 * https://bugs.kde.org/show_bug.cgi?id=375524 * https://bugs.kde.org/show_bug.cgi?id=398729 */ QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow(); if (subWindow) { QMenu *menu = subWindow->systemMenu(); if (menu && menu->actions().size() == 8) { Q_FOREACH (QAction *action, menu->actions()) { action->setShortcut(QKeySequence()); } menu->actions().last()->deleteLater(); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ if (viewManager() && viewManager()->selectionManager()) { viewManager()->selectionManager()->selectionChanged(); } KisPart *kisPart = KisPart::instance(); KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); QFontMetrics fontMetrics = docMenu->fontMetrics(); #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) QScreen *screen = qApp->screenAt(this->geometry().topLeft()); - int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); -#else int fileStringWidth = int(screen->availableGeometry().width() * .40f); +#else + int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); #endif Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = fontMetrics.elidedText(doc->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); KisResourceIterator resourceIterator(KisResourceModelProvider::resourceModel(ResourceType::Workspaces)); KisMainWindow *m_this = this; while (resourceIterator.hasNext()) { KisResourceItemSP resource = resourceIterator.next(); QAction *action = workspaceMenu->addAction(resource->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(resource->id()); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QStringList mimeTypes = KisResourceLoaderRegistry::instance()->mimeTypes(ResourceType::Workspaces); KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace(new KisWorkspaceResource("")); workspace->setDockerState(m_this->saveState()); d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); bool fileOverWriteAccepted = false; while(!fileOverWriteAccepted) { name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isNull() || name.isEmpty()) { return; } else { fileInfo = QFileInfo(saveLocation + name.split(" ").join("_") + workspace->defaultFileExtension()); if (fileInfo.exists()) { int res = QMessageBox::warning(this, i18nc("@title:window", "Name Already Exists") , i18n("The name '%1' already exists, do you wish to overwrite it?", name) , QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (res == QMessageBox::Yes) fileOverWriteAccepted = true; } else { fileOverWriteAccepted = true; } } } workspace->setFilename(fileInfo.fileName()); workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } else { text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } updateCaption(); } void KisMainWindow::updateSubwindowFlags() { bool onlyOne = false; if (d->mdiArea->subWindowList().size() == 1 && d->mdiArea->viewMode() == QMdiArea::SubWindowView) { onlyOne = true; } Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (onlyOne) { subwin->setWindowFlags(subwin->windowFlags() | Qt::FramelessWindowHint); subwin->showMaximized(); } else { subwin->setWindowFlags((subwin->windowFlags() | Qt::FramelessWindowHint) ^ Qt::FramelessWindowHint); } } } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will continue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } #ifdef Q_OS_MACOS updateSubwindowFlags(); #endif KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QString s = cfg.getMDIBackgroundColor(); KoColor c = KoColor::fromXML(s); QBrush brush(c.toQColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document, QMdiSubWindow *subWindow) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc, subWindow); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { if (d->mdiArea->currentSubWindow()) { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resourceCount() == 0) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointer KisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_MACOS w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.completeBaseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); d->toggleDetachCanvas = actionManager->createAction("view_detached_canvas"); d->toggleDetachCanvas->setChecked(false); connect(d->toggleDetachCanvas, SIGNAL(toggled(bool)), SLOT(setCanvasDetached(bool))); setCanvasDetached(false); 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->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg = d->windowStateConfig; QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QGuiApplication::screens().at(scnum)->availableVirtualGeometry(); 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 compensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::windowScreenChanged(QScreen *screen) { emit screenChanged(); d->screenConnectionsStore.clear(); d->screenConnectionsStore.addConnection(screen, SIGNAL(physicalDotsPerInchChanged(qreal)), this, SIGNAL(screenChanged())); } void KisMainWindow::slotXmlGuiMakingChanges(bool finished) { if (finished) { subWindowActivated(); } } #include diff --git a/libs/ui/KisPaletteEditor.cpp b/libs/ui/KisPaletteEditor.cpp index 88c452ffa7..1f37e36e38 100644 --- a/libs/ui/KisPaletteEditor.cpp +++ b/libs/ui/KisPaletteEditor.cpp @@ -1,686 +1,531 @@ /* * Copyright (c) 2018 Michael Zhou * * 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 #include #include #include #include #include #include #include "KisPaletteEditor.h" #include struct KisPaletteEditor::PaletteInfo { QString name; QString filename; int columnCount; - bool isGlobal; bool isReadOnly; + bool isGlobal; QHash groups; }; struct KisPaletteEditor::Private { - bool isGlobalModified {false}; bool isNameModified {false}; - bool isFilenameModified {false}; bool isColumnCountModified {false}; QSet modifiedGroupNames; // key is original group name QSet newGroupNames; QSet keepColorGroups; QSet pathsToRemove; QString groupBeingRenamed; QPointer model; QPointer view; PaletteInfo modified; QPointer query; KoResourceServer *rServer {0}; QPalette normalPalette; QPalette warnPalette; }; KisPaletteEditor::KisPaletteEditor(QObject *parent) : QObject(parent) , m_d(new Private) { m_d->rServer = KoResourceServerProvider::instance()->paletteServer(); m_d->warnPalette.setColor(QPalette::Text, Qt::red); } KisPaletteEditor::~KisPaletteEditor() { } void KisPaletteEditor::setPaletteModel(KisPaletteModel *model) { if (!model) { return; } m_d->model = model; slotPaletteChanged(); connect(model, SIGNAL(sigPaletteChanged()), SLOT(slotPaletteChanged())); connect(model, SIGNAL(sigPaletteModified()), SLOT(slotSetDocumentModified())); } void KisPaletteEditor::setView(KisViewManager *view) { m_d->view = view; } void KisPaletteEditor::addPalette() { if (!m_d->view) { return; } if (!m_d->view->document()) { return; } + KoColorSetSP colorSet(new KoColorSet()); + KoDialog dlg; QFormLayout layout; dlg.mainWidget()->setLayout(&layout); QLabel lbl(i18nc("Label for line edit to set a palette name.","Name")); QLineEdit le(i18nc("Default name for a new palette","New Palette")); layout.addRow(&lbl, &le); - QLabel lbl2(i18nc("Label for line edit to set a palette filename.","File Name")); - QLineEdit le2(i18nc("Default file name for a new palette", "New Palette")); - layout.addRow(&lbl2, &le2); + QString saveLocation = m_d->rServer->saveLocation(); QCheckBox chkSaveInDocument(i18n("Save Palette in the Current Document")); chkSaveInDocument.setChecked(false); layout.addRow(&chkSaveInDocument); if (dlg.exec() != QDialog::Accepted) { return; } - KoColorSetSP newColorSet(new KoColorSet(newPaletteFileName(!chkSaveInDocument.isChecked(), le2.text()))); - newColorSet->setPaletteType(KoColorSet::KPL); - newColorSet->setIsGlobal(!chkSaveInDocument.isChecked()); - newColorSet->setIsEditable(true); - newColorSet->setValid(true); - newColorSet->setName(le.text()); + QString name = le.text(); + colorSet->setPaletteType(KoColorSet::KPL); + colorSet->setIsEditable(true); + colorSet->setValid(true); + colorSet->setName(name); + colorSet->setFilename(name.split(" ").join("_")+colorSet->defaultFileExtension()); - m_d->rServer->addResource(newColorSet, !chkSaveInDocument.isChecked()); - //m_d->rServer->removeFromBlacklist(newColorSet); - uploadPaletteList(); + QString resourceLocation = ""; + if (chkSaveInDocument.isChecked()) { + resourceLocation = m_d->view->document()->uniqueID(); + } + m_d->rServer->resourceModel()->addResource(colorSet, resourceLocation); } void KisPaletteEditor::importPalette() { KoFileDialog dialog(0, KoFileDialog::OpenFile, "Open Palette"); dialog.setDefaultDir(QDir::homePath()); dialog.setMimeTypeFilters(QStringList() << "krita/x-colorset" << "application/x-gimp-color-palette"); QString filename = dialog.filename(); if (filename.isEmpty()) { return; } - if (duplicateExistsFilename(filename, false)) { - QMessageBox message; - message.setWindowTitle(i18n("Can't Import Palette")); - message.setText(i18n("Can't import palette: there's already imported with the same filename")); - message.exec(); - return; - } QMessageBox messageBox; messageBox.setText(i18n("Do you want to store this palette in your current image?")); messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - bool global = (messageBox.exec() == QMessageBox::Yes); + QString storageLocation = ""; + if (messageBox.exec() == QMessageBox::Yes) { + storageLocation = m_d->view->document()->uniqueID(); + } KoColorSetSP colorSet(new KoColorSet(filename)); - colorSet->load(KisGlobalResourcesInterface::instance()); - QString name = filenameFromPath(colorSet->filename()); - - if (duplicateExistsFilename(name, false)) { - colorSet->setFilename(newPaletteFileName(global)); - } else { - colorSet->setFilename(name); - } - - - colorSet->setIsGlobal(global); - m_d->rServer->addResource(colorSet, global); - //m_d->rServer->removeFromBlacklist(colorSet); - - uploadPaletteList(); + m_d->rServer->resourceModel()->addResource(colorSet, storageLocation); } void KisPaletteEditor::removePalette(KoColorSetSP cs) { if (!m_d->view) { return; } if (!m_d->view->document()) { return; } if (!cs || !cs->isEditable()) { return; } m_d->rServer->removeResourceFromServer(cs); - uploadPaletteList(); } int KisPaletteEditor::rowNumberOfGroup(const QString &oriName) const { if (!m_d->modified.groups.contains(oriName)) { return 0; } return m_d->modified.groups[oriName].rowCount(); } bool KisPaletteEditor::duplicateExistsGroupName(const QString &name) const { if (name == m_d->groupBeingRenamed) { return false; } Q_FOREACH (const KisSwatchGroup &g, m_d->modified.groups.values()) { if (name == g.name()) { return true; } } return false; } bool KisPaletteEditor::duplicateExistsOriginalGroupName(const QString &name) const { return m_d->modified.groups.contains(name); } QString KisPaletteEditor::oldNameFromNewName(const QString &newName) const { Q_FOREACH (const QString &oldGroupName, m_d->modified.groups.keys()) { if (m_d->modified.groups[oldGroupName].name() == newName) { return oldGroupName; } } return QString(); } void KisPaletteEditor::rename(const QString &newName) { if (newName.isEmpty()) { return; } m_d->isNameModified = true; m_d->modified.name = newName; } -void KisPaletteEditor::changeFilename(const QString &newName) -{ - if (newName.isEmpty()) { return; } - m_d->isFilenameModified = true; - m_d->pathsToRemove.insert(m_d->modified.filename); - if (m_d->modified.isGlobal) { - m_d->modified.filename = m_d->rServer->saveLocation() + newName; - } else { - m_d->modified.filename = newName; - } -} - void KisPaletteEditor::changeColCount(int newCount) { m_d->isColumnCountModified = true; m_d->modified.columnCount = newCount; } QString KisPaletteEditor::addGroup() { KoDialog dlg; m_d->query = &dlg; QVBoxLayout layout(&dlg); dlg.mainWidget()->setLayout(&layout); QLabel lblName(i18n("Name"), &dlg); layout.addWidget(&lblName); QLineEdit leName(&dlg); leName.setText(newGroupName()); connect(&leName, SIGNAL(textChanged(QString)), SLOT(slotGroupNameChanged(QString))); layout.addWidget(&leName); QLabel lblRowCount(i18n("Row count"), &dlg); layout.addWidget(&lblRowCount); QSpinBox spxRow(&dlg); spxRow.setValue(20); layout.addWidget(&spxRow); if (dlg.exec() != QDialog::Accepted) { return QString(); } if (duplicateExistsGroupName(leName.text())) { return QString(); } QString realName = leName.text(); QString name = realName; if (duplicateExistsOriginalGroupName(name)) { name = newGroupName(); } m_d->modified.groups[name] = KisSwatchGroup(); KisSwatchGroup &newGroup = m_d->modified.groups[name]; newGroup.setName(realName); m_d->newGroupNames.insert(name); newGroup.setRowCount(spxRow.value()); return realName; } bool KisPaletteEditor::removeGroup(const QString &name) { KoDialog window; window.setWindowTitle(i18nc("@title:window", "Removing Group")); QFormLayout editableItems(&window); QCheckBox chkKeep(&window); window.mainWidget()->setLayout(&editableItems); editableItems.addRow(i18nc("Shows up when deleting a swatch group", "Keep the Colors"), &chkKeep); if (window.exec() != KoDialog::Accepted) { return false; } m_d->modified.groups.remove(name); m_d->newGroupNames.remove(name); if (chkKeep.isChecked()) { m_d->keepColorGroups.insert(name); } return true; } QString KisPaletteEditor::renameGroup(const QString &oldName) { if (oldName.isEmpty() || oldName == KoColorSet::GLOBAL_GROUP_NAME) { return QString(); } KoDialog dlg; m_d->query = &dlg; m_d->groupBeingRenamed = m_d->modified.groups[oldName].name(); QFormLayout form(&dlg); dlg.mainWidget()->setLayout(&form); QLineEdit leNewName; connect(&leNewName, SIGNAL(textChanged(QString)), SLOT(slotGroupNameChanged(QString))); leNewName.setText(m_d->modified.groups[oldName].name()); form.addRow(i18nc("Renaming swatch group", "New name"), &leNewName); if (dlg.exec() != KoDialog::Accepted) { return QString(); } if (leNewName.text().isEmpty()) { return QString(); } if (duplicateExistsGroupName(leNewName.text())) { return QString(); } m_d->modified.groups[oldName].setName(leNewName.text()); m_d->modifiedGroupNames.insert(oldName); return leNewName.text(); } void KisPaletteEditor::slotGroupNameChanged(const QString &newName) { QLineEdit *leGroupName = qobject_cast(sender()); if (duplicateExistsGroupName(newName) || newName == QString()) { leGroupName->setPalette(m_d->warnPalette); if (m_d->query->button(KoDialog::Ok)) { m_d->query->button(KoDialog::Ok)->setEnabled(false); } return; } leGroupName->setPalette(m_d->normalPalette); if (m_d->query->button(KoDialog::Ok)) { m_d->query->button(KoDialog::Ok)->setEnabled(true); } } void KisPaletteEditor::changeGroupRowCount(const QString &name, int newRowCount) { if (!m_d->modified.groups.contains(name)) { return; } m_d->modified.groups[name].setRowCount(newRowCount); m_d->modifiedGroupNames.insert(name); } void KisPaletteEditor::setGlobal(bool isGlobal) { - m_d->isGlobalModified = true; m_d->modified.isGlobal = isGlobal; } void KisPaletteEditor::setEntry(const KoColor &color, const QModelIndex &index) { Q_ASSERT(m_d->model); if (!m_d->model->colorSet()->isEditable()) { return; } if (!m_d->view) { return; } if (!m_d->view->document()) { return; } KisSwatch c = KisSwatch(color); c.setId(QString::number(m_d->model->colorSet()->colorCount() + 1)); c.setName(i18nc("Default name for a color swatch","Color %1", QString::number(m_d->model->colorSet()->colorCount()+1))); m_d->model->setEntry(c, index); } void KisPaletteEditor::slotSetDocumentModified() { + m_d->rServer->resourceModel()->addResource(m_d->model->colorSet(), m_d->model->colorSet()->storageLocation()); m_d->view->document()->setModified(true); } void KisPaletteEditor::removeEntry(const QModelIndex &index) { Q_ASSERT(m_d->model); if (!m_d->model->colorSet()->isEditable()) { return; } if (!m_d->view) { return; } if (!m_d->view->document()) { return; } if (qvariant_cast(index.data(KisPaletteModel::IsGroupNameRole))) { removeGroup(qvariant_cast(index.data(KisPaletteModel::GroupNameRole))); - updatePalette(); } else { m_d->model->removeEntry(index, false); } - if (m_d->model->colorSet()->isGlobal()) { - m_d->model->colorSet()->save(); - return; - } + updatePalette(); } void KisPaletteEditor::modifyEntry(const QModelIndex &index) { if (!m_d->model->colorSet()->isEditable()) { return; } if (!m_d->view) { return; } if (!m_d->view->document()) { return; } KoDialog dlg; dlg.setCaption(i18nc("@title:window", "Add a Color")); QFormLayout *editableItems = new QFormLayout(&dlg); dlg.mainWidget()->setLayout(editableItems); QString groupName = qvariant_cast(index.data(Qt::DisplayRole)); if (qvariant_cast(index.data(KisPaletteModel::IsGroupNameRole))) { renameGroup(groupName); updatePalette(); } else { QLineEdit *lnIDName = new QLineEdit(&dlg); QLineEdit *lnGroupName = new QLineEdit(&dlg); KisColorButton *bnColor = new KisColorButton(&dlg); QCheckBox *chkSpot = new QCheckBox(&dlg); chkSpot->setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color.")); KisSwatch entry = m_d->model->getEntry(index); editableItems->addRow(i18n("ID"), lnIDName); editableItems->addRow(i18nc("Name for a swatch group", "Swatch group name"), lnGroupName); editableItems->addRow(i18n("Color"), bnColor); editableItems->addRow(i18n("Spot color"), chkSpot); lnGroupName->setText(entry.name()); lnIDName->setText(entry.id()); bnColor->setColor(entry.color()); chkSpot->setChecked(entry.spotColor()); if (dlg.exec() == KoDialog::Accepted) { entry.setName(lnGroupName->text()); entry.setId(lnIDName->text()); entry.setColor(bnColor->color()); entry.setSpotColor(chkSpot->isChecked()); m_d->model->setEntry(entry, index); } } } void KisPaletteEditor::addEntry(const KoColor &color) { Q_ASSERT(m_d->model); if (!m_d->view) { return; } if (!m_d->view->document()) { return; } if (!m_d->model->colorSet()->isEditable()) { return; } KoDialog window; window.setWindowTitle(i18nc("@title:window", "Add a new Colorset Entry")); QFormLayout editableItems(&window); window.mainWidget()->setLayout(&editableItems); QComboBox cmbGroups(&window); cmbGroups.addItems(m_d->model->colorSet()->getGroupNames()); QLineEdit lnIDName(&window); QLineEdit lnName(&window); KisColorButton bnColor(&window); QCheckBox chkSpot(&window); chkSpot.setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color.")); editableItems.addRow(i18n("Group"), &cmbGroups); editableItems.addRow(i18n("ID"), &lnIDName); editableItems.addRow(i18n("Name"), &lnName); editableItems.addRow(i18n("Color"), &bnColor); editableItems.addRow(i18nc("Spot color", "Spot"), &chkSpot); cmbGroups.setCurrentIndex(0); lnName.setText(i18nc("Default name for a color swatch","Color %1", QString::number(m_d->model->colorSet()->colorCount()+1))); lnIDName.setText(QString::number(m_d->model->colorSet()->colorCount() + 1)); bnColor.setColor(color); chkSpot.setChecked(false); if (window.exec() != KoDialog::Accepted) { return; } QString groupName = cmbGroups.currentText(); KisSwatch newEntry; newEntry.setColor(bnColor.color()); newEntry.setName(lnName.text()); newEntry.setId(lnIDName.text()); newEntry.setSpotColor(chkSpot.isChecked()); m_d->model->addEntry(newEntry, groupName); - - if (m_d->model->colorSet()->isGlobal()) { - m_d->model->colorSet()->save(); - return; - } + m_d->rServer->resourceModel()->updateResource(m_d->model->colorSet()); m_d->modifiedGroupNames.insert(groupName); m_d->modified.groups[groupName].addEntry(newEntry); } void KisPaletteEditor::updatePalette() { Q_ASSERT(m_d->model); Q_ASSERT(m_d->model->colorSet()); if (!m_d->model->colorSet()->isEditable()) { return; } if (!m_d->view) { return; } if (!m_d->view->document()) { return; } KoColorSetSP palette = m_d->model->colorSet(); PaletteInfo &modified = m_d->modified; if (m_d->isColumnCountModified) { palette->setColumnCount(modified.columnCount); } if (m_d->isNameModified) { - palette->setName(modified.name); + m_d->rServer->resourceModel()->renameResource(palette, modified.name); } - if (m_d->isFilenameModified) { - QString originalPath = palette->filename(); - palette->setFilename(modified.filename); - if (palette->isGlobal()) { - if (!palette->save()) { - palette->setFilename(newPaletteFileName(true)); - palette->save(); - } - QFile::remove(originalPath); - } - } - if (m_d->isGlobalModified) { - palette->setIsGlobal(modified.isGlobal); - if (modified.isGlobal) { - setGlobal(); - } else { - setNonGlobal(); - } + QString resourceLocation = m_d->model->colorSet()->storageLocation(); + if (m_d->modified.isGlobal) { + resourceLocation = QString(); + } else { + resourceLocation = m_d->view->document()->uniqueID(); } Q_FOREACH (const QString &groupName, palette->getGroupNames()) { if (!modified.groups.contains(groupName)) { m_d->model->removeGroup(groupName, m_d->keepColorGroups.contains(groupName)); } } m_d->keepColorGroups.clear(); Q_FOREACH (const QString &groupName, palette->getGroupNames()) { if (m_d->modifiedGroupNames.contains(groupName)) { m_d->model->setRowNumber(groupName, modified.groups[groupName].rowCount()); if (groupName != modified.groups[groupName].name()) { m_d->model->renameGroup(groupName, modified.groups[groupName].name()); modified.groups[modified.groups[groupName].name()] = modified.groups[groupName]; modified.groups.remove(groupName); } } } m_d->modifiedGroupNames.clear(); Q_FOREACH (const QString &newGroupName, m_d->newGroupNames) { m_d->model->addGroup(modified.groups[newGroupName]); } m_d->newGroupNames.clear(); - - if (m_d->model->colorSet()->isGlobal()) { - m_d->model->colorSet()->save(); - } + qDebug() << "updating palette from updatePalette" << m_d->model->colorSet()->filename() << resourceLocation; + m_d->rServer->resourceModel()->addResource(m_d->model->colorSet(), resourceLocation); } void KisPaletteEditor::slotPaletteChanged() { Q_ASSERT(m_d->model); if (!m_d->model->colorSet()) { return; } KoColorSetSP palette = m_d->model->colorSet(); m_d->modified.groups.clear(); m_d->keepColorGroups.clear(); m_d->newGroupNames.clear(); m_d->modifiedGroupNames.clear(); m_d->modified.name = palette->name(); - m_d->modified.filename = palette->filename(); + //hack alert! needs better solution. + m_d->modified.isGlobal = !palette->storageLocation().contains("/"); m_d->modified.columnCount = palette->columnCount(); - m_d->modified.isGlobal = palette->isGlobal(); m_d->modified.isReadOnly = !palette->isEditable(); Q_FOREACH (const QString &groupName, palette->getGroupNames()) { KisSwatchGroup *cs = palette->getGroup(groupName); m_d->modified.groups[groupName] = KisSwatchGroup(*cs); } } -void KisPaletteEditor::setGlobal() -{ - Q_ASSERT(m_d->model); - if (!m_d->view) { return; } - if (!m_d->view->document()) { return; } - if (!m_d->model->colorSet()) { return; } - - KoColorSetSP colorSet = m_d->model->colorSet(); - QString saveLocation = m_d->rServer->saveLocation(); - QString name = filenameFromPath(colorSet->filename()); - - QFileInfo fileInfo(saveLocation + name); - - colorSet->setFilename(fileInfo.filePath()); - colorSet->setIsGlobal(true); - //m_d->rServer->removeFromBlacklist(colorSet); - if (!colorSet->save()) { - QMessageBox message; - message.setWindowTitle(i18n("Saving palette failed")); - message.setText(i18n("Failed to save global palette file. Please set it to non-global, or you will lose the file when you close Krita")); - message.exec(); - } - - uploadPaletteList(); -} - -bool KisPaletteEditor::duplicateExistsFilename(const QString &filename, bool global) const -{ - QString prefix; - if (global) { - prefix = m_d->rServer->saveLocation(); - } - - Q_FOREACH (const KoResourceSP r, KoResourceServerProvider::instance()->paletteServer()->resources()) { - if (r->filename() == prefix + filename && r != m_d->model->colorSet()) { - return true; - } - } - - return false; -} - QString KisPaletteEditor::relativePathFromSaveLocation() const { return filenameFromPath(m_d->modified.filename); } -void KisPaletteEditor::setNonGlobal() -{ - Q_ASSERT(m_d->model); - if (!m_d->view) { return; } - if (!m_d->view->document()) { return; } - if (!m_d->model->colorSet()) { return; } - - KoColorSetSP colorSet = m_d->model->colorSet(); - - QString name = filenameFromPath(colorSet->filename()); - QFile::remove(colorSet->filename()); - - if (duplicateExistsFilename(name, false)) { - colorSet->setFilename(newPaletteFileName(false)); - } else { - colorSet->setFilename(name); - } - - colorSet->setIsGlobal(false); - - uploadPaletteList(); -} - -QString KisPaletteEditor::newPaletteFileName(bool isGlobal, const QString &filename) -{ - QSet nameSet; - - Q_FOREACH (const KoResourceSP r, m_d->rServer->resources()) { - nameSet.insert(r->filename()); - } - - KoColorSet tmpColorSet; - QString result = (filename.isEmpty() ? "new_palette" : filename); - - if (isGlobal) { - result = m_d->rServer->saveLocation() + result; - } - - int i = 0; - while (nameSet.contains(result + QString::number(i) + tmpColorSet.defaultFileExtension())) { - i++; - } - result = result + (i > 0 ? QString::number(i) : "") + tmpColorSet.defaultFileExtension(); - return result; -} - QString KisPaletteEditor::newGroupName() const { int i = 1; QString groupname = i18nc("Default new group name", "New Group %1", QString::number(i)); while (m_d->modified.groups.contains(groupname)) { i++; groupname = i18nc("Default new group name", "New Group %1", QString::number(i)); } return groupname; } -void KisPaletteEditor::uploadPaletteList() const -{ - QList list; - Q_FOREACH (KoResourceSP paletteResource, m_d->rServer->resources()) { - KoColorSetSP palette = paletteResource.staticCast(); - Q_ASSERT(palette); - if (!palette->isGlobal()) { - list.append(palette); - } - } - m_d->view->document()->setPaletteList(list); -} - QString KisPaletteEditor::filenameFromPath(const QString &path) const { return QDir::fromNativeSeparators(path).section('/', -1, -1); } diff --git a/libs/ui/KisPaletteEditor.h b/libs/ui/KisPaletteEditor.h index b0fc8b5e1f..41327304e5 100644 --- a/libs/ui/KisPaletteEditor.h +++ b/libs/ui/KisPaletteEditor.h @@ -1,146 +1,142 @@ /* * Copyright (c) 2018 Michael Zhou * * 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 KISPALETTEMANAGER_H #define KISPALETTEMANAGER_H #include #include #include #include class KoColorSet; class KisPaletteModel; class KisViewManager; class KisSwatchGroup; class KisViewManager; /** * @brief The PaletteEditor class * this class manipulates a KisPaletteModel using GUI elements and communicate * with KisDocument * * Changes made in this class won't be done to the palette if the palette is * read only (not editable, isEditable() == false) */ class KRITAUI_EXPORT KisPaletteEditor : public QObject { Q_OBJECT public: struct PaletteInfo; public: explicit KisPaletteEditor(QObject *parent = 0); ~KisPaletteEditor(); void setPaletteModel(KisPaletteModel *model); void setView(KisViewManager *view); void addPalette(); void importPalette(); void removePalette(KoColorSetSP ); /** * @brief rowNumberOfGroup * @param oriName the original name of a group at the creation of the instance * @return newest row number of the group */ int rowNumberOfGroup(const QString &oriName) const; /** * @brief oldNameFromNewName * @param newName the current name of a group * @return the name of the group at the creation of the instance */ QString oldNameFromNewName(const QString &newName) const; /** * @brief duplicateExistsFilename * @param filename the name of the file * @param global if this filename is going to be used for a global palette * @return true if the a palette in the resource system that has filename * name already exists else false */ bool duplicateExistsFilename(const QString &filename, bool global) const; QString relativePathFromSaveLocation() const; void rename(const QString &newName); void changeFilename(const QString &newName); void changeColCount(int); /** * @brief addGroup * @return new group's name if change accepted, empty string if cancelled */ QString addGroup(); /** * @brief removeGroup * @param name original group name * @return true if change accepted, false if cancelled */ bool removeGroup(const QString &name); /** * @brief renameGroup * @param oldName * @return new name if change accepted, empty string if cancelled */ QString renameGroup(const QString &oldName); void changeGroupRowCount(const QString &name, int newRowCount); void setGlobal(bool); void setReadOnly(bool); void setEntry(const KoColor &color, const QModelIndex &index); void removeEntry(const QModelIndex &index); void modifyEntry(const QModelIndex &index); void addEntry(const KoColor &color); bool isModified() const; /** * @brief getModifiedGroup * @param originalName name of the group at the creation of the instance * @return the modified group */ const KisSwatchGroup &getModifiedGroup(const QString &originalName) const; /** * @brief updatePalette * MUST be called to make the changes into the resource server */ void updatePalette(); private Q_SLOTS: void slotGroupNameChanged(const QString &newName); void slotPaletteChanged(); void slotSetDocumentModified(); private: - QString newPaletteFileName(bool isGlobal, const QString &filename = QString()); QString newGroupName() const; - void setNonGlobal(); - void setGlobal(); bool duplicateExistsGroupName(const QString &name) const; bool duplicateExistsOriginalGroupName(const QString &name) const; - void uploadPaletteList() const; QString filenameFromPath(const QString &path) const; private: struct Private; QScopedPointer m_d; }; #endif // KISPALETTEMANAGER_H diff --git a/libs/ui/KisPart.cpp b/libs/ui/KisPart.cpp index 418aeedcae..d8e61f9951 100644 --- a/libs/ui/KisPart.cpp +++ b/libs/ui/KisPart.cpp @@ -1,579 +1,590 @@ /* This file is part of the KDE project * Copyright (C) 1998-1999 Torben Weis * Copyright (C) 2000-2005 David Faure * Copyright (C) 2007-2008 Thorsten Zachmann * Copyright (C) 2010-2012 Boudewijn Rempt * Copyright (C) 2011 Inge Wallin * Copyright (C) 2015 Michael Abrahams * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisPart.h" #include "KoProgressProxy.h" #include #include #include #include #include #include #include #include #include "KisApplication.h" #include "KisMainWindow.h" #include "KisDocument.h" #include "KisView.h" #include "KisViewManager.h" #include "KisImportExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_shape_controller.h" #include "KisResourceServerProvider.h" #include "kis_animation_cache_populator.h" #include "kis_idle_watcher.h" #include "kis_image.h" #include "KisOpenPane.h" #include "kis_color_manager.h" #include "kis_action.h" #include "kis_action_registry.h" #include "KisSessionResource.h" #include "KisBusyWaitBroker.h" #include "dialogs/kis_delayed_save_dialog.h" Q_GLOBAL_STATIC(KisPart, s_instance) class Q_DECL_HIDDEN KisPart::Private { public: Private(KisPart *_part) : part(_part) , idleWatcher(2500) , animationCachePopulator(_part) { } ~Private() { } KisPart *part; QList > views; QList > mainWindows; QList > documents; KActionCollection *actionCollection{0}; KisIdleWatcher idleWatcher; KisAnimationCachePopulator animationCachePopulator; KisSessionResourceSP currentSession; bool closingSession{false}; QScopedPointer sessionManager; bool queryCloseDocument(KisDocument *document) { Q_FOREACH(auto view, views) { if (view && view->isVisible() && view->document() == document) { return view->queryClose(); } } return true; } }; KisPart* KisPart::instance() { return s_instance; } namespace { void busyWaitWithFeedback(KisImageSP image) { const int busyWaitDelay = 1000; KisDelayedSaveDialog dialog(image, KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, KisPart::instance()->currentMainwindow()); dialog.blockIfImageIsBusy(); } } KisPart::KisPart() : d(new Private(this)) { // Preload all the resources in the background Q_UNUSED(KoResourceServerProvider::instance()); Q_UNUSED(KisResourceServerProvider::instance()); Q_UNUSED(KisColorManager::instance()); connect(this, SIGNAL(documentOpened(QString)), this, SLOT(updateIdleWatcherConnections())); connect(this, SIGNAL(documentClosed(QString)), this, SLOT(updateIdleWatcherConnections())); connect(KisActionRegistry::instance(), SIGNAL(shortcutsUpdated()), this, SLOT(updateShortcuts())); connect(&d->idleWatcher, SIGNAL(startedIdleMode()), &d->animationCachePopulator, SLOT(slotRequestRegeneration())); d->animationCachePopulator.slotRequestRegeneration(); KisBusyWaitBroker::instance()->setFeedbackCallback(&busyWaitWithFeedback); } KisPart::~KisPart() { while (!d->documents.isEmpty()) { delete d->documents.takeFirst(); } while (!d->views.isEmpty()) { delete d->views.takeFirst(); } while (!d->mainWindows.isEmpty()) { delete d->mainWindows.takeFirst(); } delete d; } void KisPart::updateIdleWatcherConnections() { QVector images; Q_FOREACH (QPointer document, documents()) { if (document->image()) { images << document->image(); } } d->idleWatcher.setTrackedImages(images); } void KisPart::addDocument(KisDocument *document) { //dbgUI << "Adding document to part list" << document; Q_ASSERT(document); if (!d->documents.contains(document)) { d->documents.append(document); emit documentOpened('/'+objectName()); emit sigDocumentAdded(document); connect(document, SIGNAL(sigSavingFinished()), SLOT(slotDocumentSaved())); } } QList > KisPart::documents() const { return d->documents; } KisDocument *KisPart::createDocument() const { KisDocument *doc = new KisDocument(); return doc; } +KisDocument *KisPart::createTemporaryDocument() const +{ + KisDocument *doc = new KisDocument(false); + return doc; +} + int KisPart::documentCount() const { return d->documents.size(); } void KisPart::removeDocument(KisDocument *document) { d->documents.removeAll(document); emit documentClosed('/'+objectName()); emit sigDocumentRemoved(document->url().toLocalFile()); document->deleteLater(); } KisMainWindow *KisPart::createMainWindow(QUuid id) { KisMainWindow *mw = new KisMainWindow(id); dbgUI <<"mainWindow" << (void*)mw << "added to view" << this; d->mainWindows.append(mw); emit sigWindowAdded(mw); return mw; } KisView *KisPart::createView(KisDocument *document, KisViewManager *viewManager, QWidget *parent) { // If creating the canvas fails, record this and disable OpenGL next time KisConfig cfg(false); KConfigGroup grp( KSharedConfig::openConfig(), "crashprevention"); if (grp.readEntry("CreatingCanvas", false)) { cfg.disableOpenGL(); } if (cfg.canvasState() == "OPENGL_FAILED") { cfg.disableOpenGL(); } grp.writeEntry("CreatingCanvas", true); grp.sync(); QApplication::setOverrideCursor(Qt::WaitCursor); KisView *view = new KisView(document, viewManager, parent); QApplication::restoreOverrideCursor(); // Record successful canvas creation grp.writeEntry("CreatingCanvas", false); grp.sync(); addView(view); return view; } void KisPart::addView(KisView *view) { if (!view) return; if (!d->views.contains(view)) { d->views.append(view); } emit sigViewAdded(view); } void KisPart::removeView(KisView *view) { if (!view) return; /** * HACK ALERT: we check here explicitly if the document (or main * window), is saving the stuff. If we close the * document *before* the saving is completed, a crash * will happen. */ KIS_ASSERT_RECOVER_RETURN(!view->mainWindow()->hackIsSaving()); emit sigViewRemoved(view); QPointer doc = view->document(); d->views.removeAll(view); if (doc) { bool found = false; Q_FOREACH (QPointer view, d->views) { if (view && view->document() == doc) { found = true; break; } } if (!found) { removeDocument(doc); } } } QList > KisPart::views() const { return d->views; } int KisPart::viewCount(KisDocument *doc) const { if (!doc) { return d->views.count(); } else { int count = 0; Q_FOREACH (QPointer view, d->views) { if (view && view->isVisible() && view->document() == doc) { count++; } } return count; } } bool KisPart::closingSession() const { return d->closingSession; } bool KisPart::exists() { return s_instance.exists(); } bool KisPart::closeSession(bool keepWindows) { d->closingSession = true; Q_FOREACH(auto document, d->documents) { if (!d->queryCloseDocument(document.data())) { d->closingSession = false; return false; } } if (d->currentSession) { KisConfig kisCfg(false); if (kisCfg.saveSessionOnQuit(false)) { d->currentSession->storeCurrentWindows(); d->currentSession->save(); KConfigGroup cfg = KSharedConfig::openConfig()->group("session"); cfg.writeEntry("previousSession", d->currentSession->name()); } d->currentSession = nullptr; } if (!keepWindows) { Q_FOREACH (auto window, d->mainWindows) { window->close(); } if (d->sessionManager) { d->sessionManager->close(); } } d->closingSession = false; return true; } void KisPart::slotDocumentSaved() { KisDocument *doc = qobject_cast(sender()); emit sigDocumentSaved(doc->url().toLocalFile()); } void KisPart::removeMainWindow(KisMainWindow *mainWindow) { dbgUI <<"mainWindow" << (void*)mainWindow <<"removed from doc" << this; if (mainWindow) { d->mainWindows.removeAll(mainWindow); } } const QList > &KisPart::mainWindows() const { return d->mainWindows; } int KisPart::mainwindowCount() const { return d->mainWindows.count(); } KisMainWindow *KisPart::currentMainwindow() const { QWidget *widget = qApp->activeWindow(); KisMainWindow *mainWindow = qobject_cast(widget); while (!mainWindow && widget) { widget = widget->parentWidget(); mainWindow = qobject_cast(widget); } if (!mainWindow && mainWindows().size() > 0) { mainWindow = mainWindows().first(); } return mainWindow; } KisMainWindow * KisPart::windowById(QUuid id) const { Q_FOREACH(QPointer mainWindow, d->mainWindows) { if (mainWindow->id() == id) { return mainWindow; } } return nullptr; } KisIdleWatcher* KisPart::idleWatcher() const { return &d->idleWatcher; } KisAnimationCachePopulator* KisPart::cachePopulator() const { return &d->animationCachePopulator; } void KisPart::openExistingFile(const QUrl &url) { // TODO: refactor out this method! KisMainWindow *mw = currentMainwindow(); KIS_SAFE_ASSERT_RECOVER_RETURN(mw); mw->openDocument(url, KisMainWindow::None); } void KisPart::updateShortcuts() { // Update any non-UI actionCollections. That includes: // Now update the UI actions. Q_FOREACH (KisMainWindow *mainWindow, d->mainWindows) { KActionCollection *ac = mainWindow->actionCollection(); ac->updateShortcuts(); // Loop through mainWindow->actionCollections() to modify tooltips // so that they list shortcuts at the end in parentheses Q_FOREACH ( QAction* action, ac->actions()) { // Remove any existing suffixes from the tooltips. // Note this regexp starts with a space, e.g. " (Ctrl-a)" QString strippedTooltip = action->toolTip().remove(QRegExp("\\s\\(.*\\)")); // Now update the tooltips with the new shortcut info. if (action->shortcut() == QKeySequence(0)) action->setToolTip(strippedTooltip); else action->setToolTip( strippedTooltip + " (" + action->shortcut().toString() + ")"); } } } void KisPart::openTemplate(const QUrl &url) { qApp->setOverrideCursor(Qt::BusyCursor); KisDocument *document = createDocument(); bool ok = document->loadNativeFormat(url.toLocalFile()); document->setModified(false); document->undoStack()->clear(); if (ok) { QString mimeType = KisMimeDatabase::mimeTypeForFile(url.toLocalFile()); // in case this is a open document template remove the -template from the end mimeType.remove( QRegExp( "-template$" ) ); document->setMimeTypeAfterLoading(mimeType); document->resetURL(); document->setReadWrite(true); } else { if (document->errorMessage().isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1", document->localFilePath())); } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1\nReason: %2", document->localFilePath(), document->errorMessage())); } delete document; return; } addDocument(document); KisMainWindow *mw = currentMainwindow(); mw->addViewAndNotifyLoadingCompleted(document); KisOpenPane *pane = qobject_cast(sender()); if (pane) { pane->hide(); pane->deleteLater(); } qApp->restoreOverrideCursor(); } void KisPart::addRecentURLToAllMainWindows(QUrl url) { // Add to recent actions list in our mainWindows Q_FOREACH (KisMainWindow *mainWindow, d->mainWindows) { mainWindow->addRecentURL(url); } } void KisPart::startCustomDocument(KisDocument* doc) { addDocument(doc); KisMainWindow *mw = currentMainwindow(); KisOpenPane *pane = qobject_cast(sender()); if (pane) { pane->hide(); pane->deleteLater(); } mw->addViewAndNotifyLoadingCompleted(doc); } KisInputManager* KisPart::currentInputManager() { KisMainWindow *mw = currentMainwindow(); KisViewManager *manager = mw ? mw->viewManager() : 0; return manager ? manager->inputManager() : 0; } void KisPart::showSessionManager() { if (d->sessionManager.isNull()) { d->sessionManager.reset(new KisSessionManagerDialog()); } d->sessionManager->show(); d->sessionManager->activateWindow(); } void KisPart::startBlankSession() { KisMainWindow *window = createMainWindow(); window->initializeGeometry(); window->show(); } bool KisPart::restoreSession(const QString &sessionName) { if (sessionName.isNull()) return false; KoResourceServer *rserver = KisResourceServerProvider::instance()->sessionServer(); KisSessionResourceSP session = rserver->resourceByName(sessionName); if (!session || !session->valid()) return false; - session->restore(); + return restoreSession(session); +} +bool KisPart::restoreSession(KisSessionResourceSP session) +{ + session->restore(); + d->currentSession = session; return true; } void KisPart::setCurrentSession(KisSessionResourceSP session) { d->currentSession = session; } diff --git a/libs/ui/KisPart.h b/libs/ui/KisPart.h index f51debfe8b..46c3eaf09a 100644 --- a/libs/ui/KisPart.h +++ b/libs/ui/KisPart.h @@ -1,283 +1,289 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2005 David Faure Copyright (C) 2007 Thorsten Zachmann Copyright (C) 2010 Boudewijn Rempt Copyright (C) 2015 Michael Abrahams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_PART_H #define KIS_PART_H #include #include #include #include #include #include "kritaui_export.h" #include #include #include namespace KIO { } class KisAction; class KisDocument; class KisView; class KisDocument; class KisIdleWatcher; class KisAnimationCachePopulator; /** * KisPart a singleton class which provides the main entry point to the application. * Krita supports multiple documents, multiple main windows, and multiple * components. KisPart manages these resources and provides them to the rest of * Krita. It manages lists of Actions and shortcuts as well. * * The terminology comes from KParts, which is a system allowing one KDE app * to be run from inside another, like pressing F4 inside dophin to run konsole. * * Needless to say, KisPart hasn't got much to do with KParts anymore. */ class KRITAUI_EXPORT KisPart : public QObject { Q_OBJECT public: static KisPart *instance(); /** * Constructor. * * @param parent may be another KisDocument, or anything else. * Usually passed by KPluginFactory::create. */ explicit KisPart(); /** * Destructor. * * The destructor does not delete any attached KisView objects and it does not * delete the attached widget as returned by widget(). */ ~KisPart() override; // ----------------- Document management ----------------- /** * create an empty document. The document is not automatically registered with the part. */ KisDocument *createDocument() const; + /** + * create a throwaway empty document. The document does not register a resource storage + */ + KisDocument *createTemporaryDocument() const; + /** * Add the specified document to the list of documents this KisPart manages. */ void addDocument(KisDocument *document); /** * @return a list of all documents this part manages */ QList > documents() const; /** * @return number of documents this part manages. */ int documentCount() const; void removeDocument(KisDocument *document); // ----------------- MainWindow management ----------------- /** * Create a new main window. */ KisMainWindow *createMainWindow(QUuid id = QUuid()); /** * Removes a main window from the list of managed windows. * * This is called by the MainWindow after it finishes its shutdown routine. */ void removeMainWindow(KisMainWindow *mainWindow); /** * @return the list of main windows. */ const QList >& mainWindows() const; /** * @return the number of shells for the main window */ int mainwindowCount() const; void addRecentURLToAllMainWindows(QUrl url); /** * @return the currently active main window. */ KisMainWindow *currentMainwindow() const; KisMainWindow *windowById(QUuid id) const; /** * @return the application-wide KisIdleWatcher. */ KisIdleWatcher *idleWatcher() const; /** * @return the application-wide AnimationCachePopulator. */ KisAnimationCachePopulator *cachePopulator() const; public Q_SLOTS: /** * This slot loads an existing file. * @param url the file to load */ void openExistingFile(const QUrl &url); /** * This slot loads a template and deletes the sender. * @param url the template to load */ void openTemplate(const QUrl &url); /** * @brief startCustomDocument adds the given document to the document list and deletes the sender() * @param doc */ void startCustomDocument(KisDocument *doc); private Q_SLOTS: void updateIdleWatcherConnections(); void updateShortcuts(); Q_SIGNALS: /** * emitted when a new document is opened. (for the idle watcher) */ void documentOpened(const QString &ref); /** * emitted when an old document is closed. (for the idle watcher) */ void documentClosed(const QString &ref); // These signals are for libkis or sketch void sigViewAdded(KisView *view); void sigViewRemoved(KisView *view); void sigDocumentAdded(KisDocument *document); void sigDocumentSaved(const QString &url); void sigDocumentRemoved(const QString &filename); void sigWindowAdded(KisMainWindow *window); public: KisInputManager *currentInputManager(); //------------------ View management ------------------ /** * Create a new view for the document. The view is added to the list of * views, and if the document wasn't known yet, it's registered as well. */ KisView *createView(KisDocument *document, KisViewManager *viewManager, QWidget *parent); /** * Adds a view to the document. If the part doesn't know yet about * the document, it is registered. * * This calls KisView::updateReadWrite to tell the new view * whether the document is readonly or not. */ void addView(KisView *view); /** * Removes a view of the document. */ void removeView(KisView *view); /** * @return a list of views this document is displayed in */ QList > views() const; /** * @return number of views this document is displayed in */ int viewCount(KisDocument *doc) const; //------------------ Session management ------------------ void showSessionManager(); void startBlankSession(); /** * Restores a saved session by name */ bool restoreSession(const QString &sessionName); + bool restoreSession(KisSessionResourceSP session); void setCurrentSession(KisSessionResourceSP session); /** * Attempts to save the session and close all windows. * This may involve asking the user to save open files. * @return false, if closing was cancelled by the user */ bool closeSession(bool keepWindows = false); /** * Are we in the process of closing the application through closeSession(). */ bool closingSession() const; /** * This function returns true if the instance has already been initialized, * false otherwise. This to prevent premature initialization that causes crash * on android see `1fbb25506a` * @see QGlobalStatic::exists() */ static bool exists(); private Q_SLOTS: void slotDocumentSaved(); private: Q_DISABLE_COPY(KisPart) class Private; Private *const d; }; #endif diff --git a/libs/ui/KisSessionResource.cpp b/libs/ui/KisSessionResource.cpp index 7e3277e343..c107478dea 100644 --- a/libs/ui/KisSessionResource.cpp +++ b/libs/ui/KisSessionResource.cpp @@ -1,210 +1,208 @@ /* * Copyright (c) 2018 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisSessionResource.h" #include #include #include #include #include #include struct KisSessionResource::Private { struct View { QUuid windowId; QUrl file; KisPropertiesConfiguration viewConfig; KisMainWindow *getWindow() const { Q_FOREACH(KisMainWindow *window, KisPart::instance()->mainWindows()) { if (window->id() == this->windowId) return window; } return nullptr; } }; QString profileName; QVector views; }; KisSessionResource::KisSessionResource(const QString &filename) : KisWindowLayoutResource(filename) , d(new Private) {} KisSessionResource::~KisSessionResource() {} KisSessionResource::KisSessionResource(const KisSessionResource &rhs) : KisWindowLayoutResource(rhs) , d(new Private) { *this = rhs; } KisSessionResource &KisSessionResource::operator=(const KisSessionResource &rhs) { if (*this != rhs) { d->views = rhs.d->views; d->profileName = rhs.d->profileName; } return *this; } KoResourceSP KisSessionResource::clone() const { return KoResourceSP(new KisSessionResource(*this)); } void KisSessionResource::restore() { auto *kisPart = KisPart::instance(); applyLayout(); QMap documents; // Find documents which are already open so we don't need to reload them QList> oldViews = kisPart->views(); Q_FOREACH(const QPointer view, oldViews) { KisDocument *document = view->document(); const QUrl url = document->url(); documents.insert(url, document); } Q_FOREACH(auto &viewData, d->views) { QUrl url = viewData.file; KisMainWindow *window = viewData.getWindow(); if (!window) { qDebug() << "Warning: session file contains inconsistent data."; } else { KisDocument *document = documents.value(url); if (!document) { document = kisPart->createDocument(); bool ok = document->openUrl(url); if (!ok) { delete document; continue; } kisPart->addDocument(document); documents.insert(url, document); } //update profile QString profileName; profileName = d->profileName; window->viewManager()->changeAuthorProfile(profileName); window->viewManager()->slotUpdateAuthorProfileActions(); KisView *view = window->newView(document); view->restoreViewState(viewData.viewConfig); } } Q_FOREACH(QPointer view, oldViews) { view->closeView(); } - // XXX: SharedPtr breakage - kisPart->setCurrentSession(KisSessionResourceSP(this)); } QString KisSessionResource::defaultFileExtension() const { return ".ksn"; } void KisSessionResource::storeCurrentWindows() { KisPart *kisPart = KisPart::instance(); const auto &windows = kisPart->mainWindows(); setWindows(windows); d->views.clear(); Q_FOREACH(const KisView *view, kisPart->views()) { if (view->document()->url().isEmpty()) continue; auto viewData = Private::View(); viewData.windowId = view->mainWindow()->id(); viewData.file = view->document()->url(); view->saveViewState(viewData.viewConfig); d->views.append(viewData); } setValid(true); } void KisSessionResource::saveXml(QDomDocument &doc, QDomElement &root) const { KisWindowLayoutResource::saveXml(doc, root); Q_FOREACH(const auto view, d->views) { QDomElement elem = doc.createElement("view"); elem.setAttribute("window", view.windowId.toString()); elem.setAttribute("src", view.file.toString()); view.viewConfig.toXML(doc, elem); root.appendChild(elem); // Save profile KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author"); QString profileName = appAuthorGroup.readEntry("active-profile", ""); QDomElement session = doc.createElement("session"); session.setAttribute("profile", profileName); root.appendChild(session); } } void KisSessionResource::loadXml(const QDomElement &root) const { KisWindowLayoutResource::loadXml(root); d->views.clear(); for (auto viewElement = root.firstChildElement("view"); !viewElement.isNull(); viewElement = viewElement.nextSiblingElement("view")) { Private::View view; view.file = QUrl(viewElement.attribute("src")); view.windowId = QUuid(viewElement.attribute("window")); view.viewConfig.fromXML(viewElement); d->views.append(view); } //Load session d->profileName.clear(); auto sessionElement = root.firstChildElement("session"); d->profileName = QString(sessionElement.attribute("profile")); } diff --git a/libs/ui/KisSessionResource.h b/libs/ui/KisSessionResource.h index 0ad38e2ec2..b88cba70fd 100644 --- a/libs/ui/KisSessionResource.h +++ b/libs/ui/KisSessionResource.h @@ -1,56 +1,61 @@ /* * Copyright (c) 2018 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISSESSIONRESOURCE_H #define KISSESSIONRESOURCE_H #include "KisWindowLayoutResource.h" class KisSessionResource : public KisWindowLayoutResource { public: KisSessionResource(const QString &filename); ~KisSessionResource(); KisSessionResource(const KisSessionResource &rhs); KisSessionResource &operator=(const KisSessionResource &rhs); KoResourceSP clone() const override; void storeCurrentWindows(); - void restore(); + QString defaultFileExtension() const override; protected: void saveXml(QDomDocument &doc, QDomElement &root) const override; void loadXml(const QDomElement &root) const override; QPair resourceType() const override { return QPair(ResourceType::Sessions, ""); } private: + + // Only KisPart should be able to call restore() to make sure it contains the pointer to it + void restore(); + friend class KisPart; + struct Private; QScopedPointer d; }; typedef QSharedPointer KisSessionResourceSP; #endif diff --git a/libs/ui/KisWelcomePageWidget.cpp b/libs/ui/KisWelcomePageWidget.cpp index 179f67293c..be32b39b20 100644 --- a/libs/ui/KisWelcomePageWidget.cpp +++ b/libs/ui/KisWelcomePageWidget.cpp @@ -1,459 +1,459 @@ /* This file is part of the KDE project * Copyright (C) 2018 Scott Petrovic * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisWelcomePageWidget.h" #include #include #include #include #include #include #include #include "kis_action_manager.h" #include "kactioncollection.h" #include "kis_action.h" #include "KConfigGroup" #include "KSharedConfig" #include #include #include "kis_icon_utils.h" #include "krita_utils.h" #include "KoStore.h" #include "kis_config.h" #include "KisDocument.h" #include #include #include KisWelcomePageWidget::KisWelcomePageWidget(QWidget *parent) : QWidget(parent) { setupUi(this); recentDocumentsListView->setDragEnabled(false); recentDocumentsListView->viewport()->setAutoFillBackground(false); recentDocumentsListView->setSpacing(2); // set up URLs that go to web browser manualLink->setTextFormat(Qt::RichText); manualLink->setTextInteractionFlags(Qt::TextBrowserInteraction); manualLink->setOpenExternalLinks(true); gettingStartedLink->setTextFormat(Qt::RichText); gettingStartedLink->setTextInteractionFlags(Qt::TextBrowserInteraction); gettingStartedLink->setOpenExternalLinks(true); supportKritaLink->setTextFormat(Qt::RichText); supportKritaLink->setTextInteractionFlags(Qt::TextBrowserInteraction); supportKritaLink->setOpenExternalLinks(true); userCommunityLink->setTextFormat(Qt::RichText); userCommunityLink->setTextInteractionFlags(Qt::TextBrowserInteraction); userCommunityLink->setOpenExternalLinks(true); kritaWebsiteLink->setTextFormat(Qt::RichText); kritaWebsiteLink->setTextInteractionFlags(Qt::TextBrowserInteraction); kritaWebsiteLink->setOpenExternalLinks(true); sourceCodeLink->setTextFormat(Qt::RichText); sourceCodeLink->setTextInteractionFlags(Qt::TextBrowserInteraction); sourceCodeLink->setOpenExternalLinks(true); poweredByKDELink->setTextFormat(Qt::RichText); poweredByKDELink->setTextInteractionFlags(Qt::TextBrowserInteraction); poweredByKDELink->setOpenExternalLinks(true); kdeIcon->setIconSize(QSize(20, 20)); kdeIcon->setIcon(KisIconUtils::loadIcon(QStringLiteral("kde")).pixmap(20)); versionNotificationLabel->setTextFormat(Qt::RichText); versionNotificationLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); versionNotificationLabel->setOpenExternalLinks(true); connect(chkShowNews, SIGNAL(toggled(bool)), newsWidget, SLOT(toggleNews(bool))); connect(newsWidget, SIGNAL(newsDataChanged()), this, SLOT(slotUpdateVersionMessage())); #ifdef Q_OS_ANDROID // checking this widgets crashes the app, so it is better for it to be hidden for now newsWidget->hide(); helpTitleLabel_2->hide(); chkShowNews->hide(); #endif // configure the News area KisConfig cfg(true); bool m_getNews = cfg.readEntry("FetchNews", false); chkShowNews->setChecked(m_getNews); setAcceptDrops(true); } KisWelcomePageWidget::~KisWelcomePageWidget() { } void KisWelcomePageWidget::setMainWindow(KisMainWindow* mainWin) { if (mainWin) { m_mainWindow = mainWin; // set the shortcut links from actions (only if a shortcut exists) if ( mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString() != "") { newFileLinkShortcut->setText(QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_new")->shortcut().toString() + QString(")")); } if (mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString() != "") { openFileShortcut->setText(QString("(") + mainWin->viewManager()->actionManager()->actionByName("file_open")->shortcut().toString() + QString(")")); } connect(recentDocumentsListView, SIGNAL(clicked(QModelIndex)), this, SLOT(recentDocumentClicked(QModelIndex))); // we need the view manager to actually call actions, so don't create the connections // until after the view manager is set connect(newFileLink, SIGNAL(clicked(bool)), this, SLOT(slotNewFileClicked())); connect(openFileLink, SIGNAL(clicked(bool)), this, SLOT(slotOpenFileClicked())); connect(clearRecentFilesLink, SIGNAL(clicked(bool)), mainWin, SLOT(clearRecentFiles())); slotUpdateThemeColors(); // allows RSS news items to apply analytics tracking. newsWidget->setAnalyticsTracking("?" + analyticsString); } } void KisWelcomePageWidget::showDropAreaIndicator(bool show) { if (!show) { QString dropFrameStyle = "QFrame#dropAreaIndicator { border: 0px }"; dropFrameBorder->setStyleSheet(dropFrameStyle); } else { QColor textColor = qApp->palette().color(QPalette::Text); QColor backgroundColor = qApp->palette().color(QPalette::Background); QColor blendedColor = KritaUtils::blendColors(textColor, backgroundColor, 0.8); // QColor.name() turns it into a hex/web format QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 2px dotted ").append(blendedColor.name()).append(" }") ; dropFrameBorder->setStyleSheet(dropFrameStyle); } } void KisWelcomePageWidget::slotUpdateThemeColors() { textColor = qApp->palette().color(QPalette::Text); backgroundColor = qApp->palette().color(QPalette::Background); // make the welcome screen labels a subtle color so it doesn't clash with the main UI elements blendedColor = KritaUtils::blendColors(textColor, backgroundColor, 0.8); blendedStyle = QString("color: ").append(blendedColor.name()); // what labels to change the color... startTitleLabel->setStyleSheet(blendedStyle); recentDocumentsLabel->setStyleSheet(blendedStyle); helpTitleLabel->setStyleSheet(blendedStyle); newFileLinkShortcut->setStyleSheet(blendedStyle); openFileShortcut->setStyleSheet(blendedStyle); clearRecentFilesLink->setStyleSheet(blendedStyle); recentDocumentsListView->setStyleSheet(blendedStyle); newFileLink->setStyleSheet(blendedStyle); openFileLink->setStyleSheet(blendedStyle); // giving the drag area messaging a dotted border QString dottedBorderStyle = QString("border: 2px dotted ").append(blendedColor.name()).append("; color:").append(blendedColor.name()).append( ";"); dragImageHereLabel->setStyleSheet(dottedBorderStyle); // make drop area QFrame have a dotted line dropFrameBorder->setObjectName("dropAreaIndicator"); QString dropFrameStyle = QString("QFrame#dropAreaIndicator { border: 4px dotted ").append(blendedColor.name()).append("}"); dropFrameBorder->setStyleSheet(dropFrameStyle); // only show drop area when we have a document over the empty area showDropAreaIndicator(false); // add icons for new and open settings to make them stand out a bit more openFileLink->setIconSize(QSize(30, 30)); newFileLink->setIconSize(QSize(30, 30)); openFileLink->setIcon(KisIconUtils::loadIcon("document-open")); newFileLink->setIcon(KisIconUtils::loadIcon("document-new")); kdeIcon->setIcon(KisIconUtils::loadIcon(QStringLiteral("kde")).pixmap(20)); // HTML links seem to be a bit more stubborn with theme changes... setting inline styles to help with color change userCommunityLink->setText(QString("") .append(i18n("User Community")).append("")); gettingStartedLink->setText(QString("") .append(i18n("Getting Started")).append("")); manualLink->setText(QString("") .append(i18n("User Manual")).append("")); supportKritaLink->setText(QString("") .append(i18n("Support Krita")).append("")); kritaWebsiteLink->setText(QString("") .append(i18n("Krita Website")).append("")); sourceCodeLink->setText(QString("") .append(i18n("Source Code")).append("")); poweredByKDELink->setText(QString("") .append(i18n("Powered by KDE")).append("")); slotUpdateVersionMessage(); // text set from RSS feed // re-populate recent files since they might have themed icons populateRecentDocuments(); } void KisWelcomePageWidget::populateRecentDocuments() { m_recentFilesModel.clear(); // clear existing data before it gets re-populated // grab recent files data int numRecentFiles = m_mainWindow->recentFilesUrls().length() > 5 ? 5 : m_mainWindow->recentFilesUrls().length(); // grab at most 5 for (int i = 0; i < numRecentFiles; i++ ) { QStandardItem *recentItem = new QStandardItem(1,2); // 1 row, 1 column recentItem->setIcon(KisIconUtils::loadIcon("document-export")); QString recentFileUrlPath = m_mainWindow->recentFilesUrls().at(i).toLocalFile(); QString fileName = recentFileUrlPath.split("/").last(); QList brokenUrls; if (m_thumbnailMap.contains(recentFileUrlPath)) { recentItem->setIcon(m_thumbnailMap[recentFileUrlPath]); } else { QFileInfo fi(recentFileUrlPath); if (fi.exists()) { QString mimeType = KisMimeDatabase::mimeTypeForFile(recentFileUrlPath); if (mimeType == KisDocument::nativeFormatMimeType() || mimeType == "image/openraster") { QScopedPointer store(KoStore::createStore(recentFileUrlPath, KoStore::Read)); if (store) { QString thumbnailpath; if (store->hasFile(QString("Thumbnails/thumbnail.png"))){ thumbnailpath = QString("Thumbnails/thumbnail.png"); } else if (store->hasFile(QString("preview.png"))) { thumbnailpath = QString("preview.png"); } if (!thumbnailpath.isEmpty()) { if (store->open(thumbnailpath)) { QByteArray bytes = store->read(store->size()); store->close(); QImage img; img.loadFromData(bytes); img.setDevicePixelRatio(devicePixelRatioF()); recentItem->setIcon(QIcon(QPixmap::fromImage(img))); } } } else { brokenUrls << m_mainWindow->recentFilesUrls().at(i); } } else if (mimeType == "image/tiff" || mimeType == "image/x-tiff") { // Workaround for a bug in Qt tiff QImageIO plugin QScopedPointer doc; - doc.reset(KisPart::instance()->createDocument()); + doc.reset(KisPart::instance()->createTemporaryDocument()); doc->setFileBatchMode(true); bool r = doc->openUrl(QUrl::fromLocalFile(recentFileUrlPath), KisDocument::DontAddToRecent); if (r) { KisPaintDeviceSP projection = doc->image()->projection(); recentItem->setIcon(QIcon(QPixmap::fromImage(projection->createThumbnail(48, 48, projection->exactBounds())))); } else { brokenUrls << m_mainWindow->recentFilesUrls().at(i); } } else { QImage img; img.setDevicePixelRatio(devicePixelRatioF()); img.load(recentFileUrlPath); if (!img.isNull()) { recentItem->setIcon(QIcon(QPixmap::fromImage(img.scaledToWidth(48)))); } else { brokenUrls << m_mainWindow->recentFilesUrls().at(i); } } if (brokenUrls.size() > 0 && brokenUrls.last().toLocalFile() != recentFileUrlPath) { m_thumbnailMap[recentFileUrlPath] = recentItem->icon(); } } } Q_FOREACH(const QUrl &url, brokenUrls) { m_mainWindow->removeRecentUrl(url); } // set the recent object with the data if (brokenUrls.isEmpty() || brokenUrls.last().toLocalFile() != recentFileUrlPath) { recentItem->setText(fileName); // what to display for the item recentItem->setToolTip(recentFileUrlPath); m_recentFilesModel.appendRow(recentItem); } } // hide clear and Recent files title if there are none bool hasRecentFiles = m_mainWindow->recentFilesUrls().length() > 0; recentDocumentsLabel->setVisible(hasRecentFiles); clearRecentFilesLink->setVisible(hasRecentFiles); recentDocumentsListView->setIconSize(QSize(48, 48)); recentDocumentsListView->setModel(&m_recentFilesModel); } void KisWelcomePageWidget::slotUpdateVersionMessage() { alertIcon->setIcon(KisIconUtils::loadIcon("warning")); alertIcon->setVisible(false); // find out if we need an update...or if this is a development version: // dev builds contain GIT hash in it and the word git // stable versions do not contain this. if (qApp->applicationVersion().contains("git")) { // Development build QString versionLabelText = QString("") .append(i18n("DEV BUILD")).append(""); versionNotificationLabel->setText(versionLabelText); alertIcon->setVisible(true); versionNotificationLabel->setVisible(true); } else if (newsWidget->hasUpdateAvailable()) { // build URL for label QString versionLabelText = QString("versionLink() + "?" + analyticsString + "version-update" + "\">") .append(i18n("New Version Available!")).append(""); versionNotificationLabel->setVisible(true); versionNotificationLabel->setText(versionLabelText); alertIcon->setVisible(true); } else { // no message needed... exit versionNotificationLabel->setVisible(false); return; } if (!blendedStyle.isNull()) { versionNotificationLabel->setStyleSheet(blendedStyle); } } void KisWelcomePageWidget::dragEnterEvent(QDragEnterEvent *event) { //qDebug() << "dragEnterEvent formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage(); showDropAreaIndicator(true); if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisWelcomePageWidget::dropEvent(QDropEvent *event) { //qDebug() << "KisWelcomePageWidget::dropEvent() formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage(); showDropAreaIndicator(false); if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) { Q_FOREACH (const QUrl &url, event->mimeData()->urls()) { if (url.toLocalFile().endsWith(".bundle")) { bool r = m_mainWindow->installBundle(url.toLocalFile()); if (!r) { qWarning() << "Could not install bundle" << url.toLocalFile(); } } else { m_mainWindow->openDocument(url, KisMainWindow::None); } } } } void KisWelcomePageWidget::dragMoveEvent(QDragMoveEvent *event) { //qDebug() << "dragMoveEvent"; m_mainWindow->dragMoveEvent(event); if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisWelcomePageWidget::dragLeaveEvent(QDragLeaveEvent */*event*/) { //qDebug() << "dragLeaveEvent"; showDropAreaIndicator(false); m_mainWindow->dragLeave(); } void KisWelcomePageWidget::recentDocumentClicked(QModelIndex index) { QString fileUrl = index.data(Qt::ToolTipRole).toString(); m_mainWindow->openDocument(QUrl::fromLocalFile(fileUrl), KisMainWindow::None ); } void KisWelcomePageWidget::slotNewFileClicked() { m_mainWindow->slotFileNew(); } void KisWelcomePageWidget::slotOpenFileClicked() { m_mainWindow->slotFileOpen(); } diff --git a/libs/ui/dialogs/KisDlgPaletteEditor.cpp b/libs/ui/dialogs/KisDlgPaletteEditor.cpp index 7d7cf09f6f..302e6b244a 100644 --- a/libs/ui/dialogs/KisDlgPaletteEditor.cpp +++ b/libs/ui/dialogs/KisDlgPaletteEditor.cpp @@ -1,235 +1,208 @@ /* * Copyright (c) 2018 Michael Zhou * * 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 #include #include #include "KisPaletteEditor.h" #include "ui_WdgDlgPaletteEditor.h" #include "KisDlgPaletteEditor.h" KisDlgPaletteEditor::KisDlgPaletteEditor() : m_ui(new Ui_WdgDlgPaletteEditor) , m_actAddGroup(new QAction(i18n("Add a group"))) , m_actDelGroup(new QAction(i18n("Delete this group"))) , m_actRenGroup(new QAction(i18n("Rename this group"))) , m_globalButtons(new QButtonGroup(this)) , m_paletteEditor(new KisPaletteEditor(this)) , m_currentGroupOriginalName(KoColorSet::GLOBAL_GROUP_NAME) { setWindowTitle(i18n("Palette Editor")); m_ui->setupUi(this); m_ui->gbxPalette->setTitle(i18n("Palette settings")); - m_ui->labelFilename->setText(i18n("Filename")); m_ui->labelName->setText(i18n("Palette Name")); m_ui->bnAddGroup->setDefaultAction(m_actAddGroup.data()); m_ui->gbxGroup->setTitle(i18n("Group settings")); m_ui->labelColCount->setText(i18n("Column count")); m_ui->labelRowCount->setText(i18n("Row count")); m_ui->bnDelGroup->setDefaultAction(m_actDelGroup.data()); m_ui->bnRenGroup->setDefaultAction(m_actRenGroup.data()); connect(m_actAddGroup.data(), SIGNAL(triggered(bool)), SLOT(slotAddGroup())); connect(m_actDelGroup.data(), SIGNAL(triggered(bool)), SLOT(slotDelGroup())); connect(m_actRenGroup.data(), SIGNAL(triggered(bool)), SLOT(slotRenGroup())); connect(m_ui->spinBoxRow, SIGNAL(valueChanged(int)), SLOT(slotRowCountChanged(int))); connect(m_ui->spinBoxCol, SIGNAL(valueChanged(int)), SLOT(slotColCountChanged(int))); connect(m_ui->lineEditName, SIGNAL(editingFinished()), SLOT(slotNameChanged())); - connect(m_ui->lineEditFilename, SIGNAL(textEdited(QString)), SLOT(slotFilenameChanged(QString))); - connect(m_ui->lineEditFilename, SIGNAL(editingFinished()), SLOT(slotFilenameInputFinished())); m_globalButtons->addButton(m_ui->rb_global, 0); m_globalButtons->addButton(m_ui->rb_document, 1); connect(m_globalButtons.data(), SIGNAL(buttonClicked(int)), SLOT(slotSetGlobal())); connect(this, SIGNAL(accepted()), SLOT(slotAccepted())); m_warnPalette.setColor(QPalette::Text, Qt::red); } KisDlgPaletteEditor::~KisDlgPaletteEditor() { } void KisDlgPaletteEditor::setPaletteModel(KisPaletteModel *model) { m_colorSet = model->colorSet(); if (m_colorSet.isNull()) { return; } m_paletteEditor->setPaletteModel(model); // don't let editor make changes when initializing - const QSignalBlocker blocker1(m_ui->lineEditFilename); const QSignalBlocker blocker2(m_ui->lineEditName); const QSignalBlocker blocker3(m_ui->spinBoxCol); const QSignalBlocker blocker4(m_ui->spinBoxRow); const QSignalBlocker blocker5(m_ui->rb_global); const QSignalBlocker blocker6(m_ui->rb_document); const QSignalBlocker blocker7(m_ui->cbxGroup); m_ui->lineEditName->setText(m_colorSet->name()); - m_ui->lineEditFilename->setText(m_paletteEditor->relativePathFromSaveLocation()); m_ui->spinBoxCol->setValue(m_colorSet->columnCount()); - if (m_colorSet->isGlobal()) { + //Hack alert!!! + //TODO: replace with a storage selector. + if (m_colorSet->storageLocation().contains("/")) { m_globalButtons->button(0)->setChecked(true); } else { m_globalButtons->button(1)->setChecked(true); } Q_FOREACH (const QString & groupName, m_colorSet->getGroupNames()) { m_ui->cbxGroup->addItem(groupName); } connect(m_ui->cbxGroup, SIGNAL(currentTextChanged(QString)), SLOT(slotGroupChosen(QString))); m_ui->cbxGroup->setCurrentText(KoColorSet::GLOBAL_GROUP_NAME); m_ui->bnDelGroup->setEnabled(false); m_ui->bnRenGroup->setEnabled(false); m_ui->spinBoxRow->setValue(m_paletteEditor->rowNumberOfGroup(KoColorSet::GLOBAL_GROUP_NAME)); bool canWrite = m_colorSet->isEditable(); m_ui->lineEditName->setEnabled(canWrite); - m_ui->lineEditFilename->setEnabled(canWrite); m_ui->spinBoxCol->setEnabled(canWrite); m_ui->spinBoxRow->setEnabled(canWrite); m_ui->rb_global->setEnabled(canWrite); m_ui->rb_document->setEnabled(canWrite); m_ui->bnAddGroup->setEnabled(canWrite); } void KisDlgPaletteEditor::setView(KisViewManager *view) { m_paletteEditor->setView(view); } void KisDlgPaletteEditor::slotAddGroup() { QString newGroupName = m_paletteEditor->addGroup(); if (!newGroupName.isEmpty()) { m_ui->cbxGroup->addItem(newGroupName); m_ui->cbxGroup->setCurrentIndex(m_ui->cbxGroup->count() - 1); }; } void KisDlgPaletteEditor::slotRenGroup() { QString newName = m_paletteEditor->renameGroup(m_currentGroupOriginalName); if (!newName.isEmpty()) { int idx = m_ui->cbxGroup->currentIndex(); m_ui->cbxGroup->removeItem(idx); m_ui->cbxGroup->insertItem(idx, newName); m_ui->cbxGroup->setCurrentIndex(idx); } } void KisDlgPaletteEditor::slotDelGroup() { int deletedIdx = m_ui->cbxGroup->currentIndex(); if (m_paletteEditor->removeGroup(m_currentGroupOriginalName)) { m_ui->cbxGroup->setCurrentIndex(0); m_ui->cbxGroup->removeItem(deletedIdx); } } void KisDlgPaletteEditor::slotGroupChosen(const QString &groupName) { if (groupName == KoColorSet::GLOBAL_GROUP_NAME) { m_ui->bnDelGroup->setEnabled(false); m_ui->bnRenGroup->setEnabled(false); } else { m_ui->bnDelGroup->setEnabled(true); m_ui->bnRenGroup->setEnabled(true); } m_currentGroupOriginalName = m_paletteEditor->oldNameFromNewName(groupName); m_ui->spinBoxRow->setValue(m_paletteEditor->rowNumberOfGroup(m_currentGroupOriginalName)); } void KisDlgPaletteEditor::slotRowCountChanged(int newCount) { m_paletteEditor->changeGroupRowCount(m_currentGroupOriginalName, newCount); } void KisDlgPaletteEditor::slotSetGlobal() { bool toGlobal = false; if (m_ui->rb_global->isChecked()) { toGlobal = true; } m_paletteEditor->setGlobal(toGlobal); } void KisDlgPaletteEditor::slotNameChanged() { m_paletteEditor->rename(qobject_cast(sender())->text()); } -void KisDlgPaletteEditor::slotFilenameChanged(const QString &newFilename) -{ - bool global = m_colorSet->isGlobal(); - if (m_paletteEditor->duplicateExistsFilename(newFilename, global)) { - m_ui->lineEditFilename->setPalette(m_warnPalette); - m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - return; - } - m_ui->lineEditFilename->setPalette(m_normalPalette); - m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - m_paletteEditor->changeFilename(newFilename); -} - -void KisDlgPaletteEditor::slotFilenameInputFinished() -{ - QString newName = m_ui->lineEditFilename->text(); - bool global = m_colorSet->isGlobal(); - if (m_paletteEditor->duplicateExistsFilename(newName, global)) { - return; - } - m_paletteEditor->changeFilename(newName); -} - void KisDlgPaletteEditor::slotColCountChanged(int newCount) { m_paletteEditor->changeColCount(newCount); } void KisDlgPaletteEditor::slotAccepted() { m_paletteEditor->updatePalette(); } diff --git a/libs/ui/dialogs/KisDlgPaletteEditor.h b/libs/ui/dialogs/KisDlgPaletteEditor.h index fef0c28dca..a14b7aa70a 100644 --- a/libs/ui/dialogs/KisDlgPaletteEditor.h +++ b/libs/ui/dialogs/KisDlgPaletteEditor.h @@ -1,97 +1,95 @@ /* * Copyright (c) 2018 Michael Zhou * * 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 KISDLGPALETTEEDITOR_H #define KISDLGPALETTEEDITOR_H #include #include #include #include #include #include #include #include #include "kritaui_export.h" class QAction; class KoColorSet; class KisPaletteModel; class KisSwatchGroup; class KoDialog; class KisViewManager; class KisPaletteEditor; class Ui_WdgDlgPaletteEditor; /** * @brief The KisDlgPaletteEditor class * a dialog used by the palette docker to make modifications to a palette. * it automatically uploads all changes into the resource server when * the change is accepted */ class KRITAUI_EXPORT KisDlgPaletteEditor : public QDialog { Q_OBJECT public: explicit KisDlgPaletteEditor(); ~KisDlgPaletteEditor(); public: void setPaletteModel(KisPaletteModel *); KoColorSetSP palette() const { return m_colorSet; } void setView(KisViewManager *); private Q_SLOTS: void slotDelGroup(); void slotAddGroup(); void slotRenGroup(); void slotGroupChosen(const QString &groupName); void slotRowCountChanged(int); void slotSetGlobal(); void slotNameChanged(); - void slotFilenameChanged(const QString &newFilename); - void slotFilenameInputFinished(); void slotColCountChanged(int); void slotAccepted(); private: QString oldNameFromNewName(const QString &newName) const; private: QScopedPointer m_ui; QScopedPointer m_actAddGroup; QScopedPointer m_actDelGroup; QScopedPointer m_actRenGroup; QScopedPointer m_globalButtons; QScopedPointer m_paletteEditor; QSharedPointer m_colorSet; QString m_currentGroupOriginalName; QPalette m_normalPalette; QPalette m_warnPalette; }; #endif // KISKisDlgPaletteEditor_H diff --git a/libs/ui/dialogs/KisSessionManagerDialog.cpp b/libs/ui/dialogs/KisSessionManagerDialog.cpp index 4483481e17..75ddb0064a 100644 --- a/libs/ui/dialogs/KisSessionManagerDialog.cpp +++ b/libs/ui/dialogs/KisSessionManagerDialog.cpp @@ -1,164 +1,164 @@ /* * Copyright (c) 2018 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "KisSessionManagerDialog.h" KisSessionManagerDialog::KisSessionManagerDialog(QWidget *parent) : QDialog(parent) { setupUi(this); - updateSessionList(); - connect(btnNew, SIGNAL(clicked()), this, SLOT(slotNewSession())); connect(btnRename, SIGNAL(clicked()), this, SLOT(slotRenameSession())); connect(btnSwitchTo, SIGNAL(clicked()), this, SLOT(slotSwitchSession())); connect(btnDelete, SIGNAL(clicked()), this, SLOT(slotDeleteSession())); connect(btnClose, SIGNAL(clicked()), this, SLOT(slotClose())); - connect(lstSessions, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(slotSessionDoubleClicked(QListWidgetItem*))); -} -void KisSessionManagerDialog::updateSessionList() { - KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); + m_model = KisResourceModelProvider::resourceModel(ResourceType::Sessions); + lstSessions->setModel(m_model); + lstSessions->setModelColumn(KisResourceModel::Name); + + + connect(m_model, SIGNAL(beforeResourcesLayoutReset(QModelIndex)), this, SLOT(slotModelAboutToBeReset(QModelIndex))); + connect(m_model, SIGNAL(afterResourcesLayoutReset()), this, SLOT(slotModelReset())); + + connect(lstSessions, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotSessionDoubleClicked(QModelIndex))); + - lstSessions->clear(); - Q_FOREACH(KisSessionResourceSP session, server->resources()) { - lstSessions->addItem(session->name()); - } } void KisSessionManagerDialog::slotNewSession() { QString name; KisSessionResourceSP session(new KisSessionResource(QString())); KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); QString saveLocation = server->saveLocation(); QFileInfo fileInfo(saveLocation + name.split(" ").join("_") + session->defaultFileExtension()); bool fileOverwriteAccepted = false; while(!fileOverwriteAccepted) { name = QInputDialog::getText(this, i18n("Create session"), i18n("Session name:"), QLineEdit::Normal, name); if (name.isNull() || name.isEmpty()) { return; } else { fileInfo = QFileInfo(saveLocation + name.split(" ").join("_") + session->defaultFileExtension()); if (fileInfo.exists()) { int res = QMessageBox::warning(this, i18nc("@title:window", "Name Already Exists") , i18n("The name '%1' already exists, do you wish to overwrite it?", name) , QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (res == QMessageBox::Yes) fileOverwriteAccepted = true; } else { fileOverwriteAccepted = true; } } } session->setFilename(fileInfo.fileName()); session->setName(name); session->storeCurrentWindows(); server->addResource(session); KisPart::instance()->setCurrentSession(session); - - updateSessionList(); } void KisSessionManagerDialog::slotRenameSession() { QString name = QInputDialog::getText(this, i18n("Rename session"), i18n("New name:"), QLineEdit::Normal ); if (name.isNull() || name.isEmpty()) return; KisSessionResourceSP session = getSelectedSession(); if (!session) return; - session->setName(name); - session->save(); - - updateSessionList(); + m_model->renameResource(session, name); } -void KisSessionManagerDialog::slotSessionDoubleClicked(QListWidgetItem* /*item*/) +void KisSessionManagerDialog::slotSessionDoubleClicked(QModelIndex /*item*/) { slotSwitchSession(); slotClose(); } void KisSessionManagerDialog::slotSwitchSession() { KisSessionResourceSP session = getSelectedSession(); if (session) { bool closed = KisPart::instance()->closeSession(true); if (closed) { - session->restore(); + KisPart::instance()->restoreSession(session); } } } KisSessionResourceSP KisSessionManagerDialog::getSelectedSession() const { - QListWidgetItem *item = lstSessions->currentItem(); - if (item) { + QModelIndex idx = lstSessions->currentIndex(); + if (idx.isValid()) { KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); - return server->resourceByName(item->text()); + QString name = m_model->data(idx, Qt::UserRole + KisResourceModel::Name).toString(); + return server->resourceByName(name); } return nullptr; } void KisSessionManagerDialog::slotDeleteSession() { - KisSessionResourceSP session = getSelectedSession(); - if (!session) return; - - if (QMessageBox::warning(this, - i18nc("@title:window", "Krita"), - QString(i18n("Permanently delete session %1?", session->name())), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - - KisPart::instance()->setCurrentSession(0); - const QString filename = session->filename(); - - KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); - server->removeResourceFromServer(session); - - QFile file(filename); - file.remove(); - - updateSessionList(); + QModelIndex idx = lstSessions->currentIndex(); + if (idx.isValid()) { + m_model->removeResource(lstSessions->currentIndex()); } } void KisSessionManagerDialog::slotClose() { hide(); } + +void KisSessionManagerDialog::slotModelAboutToBeReset(QModelIndex) +{ + QModelIndex idx = lstSessions->currentIndex(); + if (idx.isValid()) { + m_lastSessionId = m_model->data(idx, Qt::UserRole + KisResourceModel::Id).toInt(); + } +} + +void KisSessionManagerDialog::slotModelReset() +{ + for (int i = 0; i < m_model->rowCount(); i++) { + QModelIndex idx = m_model->index(i, 0); + int id = m_model->data(idx, Qt::UserRole + KisResourceModel::Id).toInt(); + if (id == m_lastSessionId) { + lstSessions->setCurrentIndex(idx); + } + } +} diff --git a/libs/ui/dialogs/KisSessionManagerDialog.h b/libs/ui/dialogs/KisSessionManagerDialog.h index 48a0305c98..6e5f0a34f0 100644 --- a/libs/ui/dialogs/KisSessionManagerDialog.h +++ b/libs/ui/dialogs/KisSessionManagerDialog.h @@ -1,50 +1,59 @@ /* * Copyright (c) 2018 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISSESSIONMANAGERDIALOG_H #define KISSESSIONMANAGERDIALOG_H #include #include "ui_wdgsessionmanager.h" #include +class KisResourceModel; + class KisSessionManagerDialog : public QDialog, Ui::DlgSessionManager { Q_OBJECT public: explicit KisSessionManagerDialog(QWidget *parent = nullptr); private Q_SLOTS: void slotNewSession(); void slotRenameSession(); void slotSwitchSession(); void slotDeleteSession(); - void slotSessionDoubleClicked(QListWidgetItem* item); + void slotSessionDoubleClicked(QModelIndex item); void slotClose(); + void slotModelAboutToBeReset(QModelIndex); + void slotModelReset(); + + private: - void updateSessionList(); KisSessionResourceSP getSelectedSession() const; + + int m_lastSessionId; + + KisResourceModel* m_model; }; #endif diff --git a/libs/ui/forms/WdgDlgPaletteEditor.ui b/libs/ui/forms/WdgDlgPaletteEditor.ui index f22c584c6b..98100fa8f2 100644 --- a/libs/ui/forms/WdgDlgPaletteEditor.ui +++ b/libs/ui/forms/WdgDlgPaletteEditor.ui @@ -1,217 +1,207 @@ WdgDlgPaletteEditor 0 0 524 481 Dialog Palette settings Palette Name - - - - File name - - - - - - Column count Resource Folder true Document Qt::Vertical 20 40 0 0 Add a group Group settings Row count Qt::Vertical 20 40 0 0 Rename this group 0 0 Delete this group Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox rejected() WdgDlgPaletteEditor reject() 316 260 286 274 buttonBox accepted() WdgDlgPaletteEditor accept() 248 254 157 274 diff --git a/libs/ui/forms/wdgpaintopsettings.ui b/libs/ui/forms/wdgpaintopsettings.ui index 2ceb4d3b82..f40f220c1f 100644 --- a/libs/ui/forms/wdgpaintopsettings.ui +++ b/libs/ui/forms/wdgpaintopsettings.ui @@ -1,949 +1,949 @@ WdgPaintOpSettings 0 0 1203 431 Brush Editor 9 1 0 0 55 55 true 0 0 320 60 320 60 false Qt::DefaultContextMenu QFrame::StyledPanel QFrame::Raised 0 Qt::ScrollBarAlwaysOff Qt::ScrollBarAlwaysOff true Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop QPainter::Antialiasing|QPainter::HighQualityAntialiasing|QPainter::SmoothPixmapTransform QGraphicsView::AnchorViewCenter 0 10 0 0 10 75 true - current brush + Current brush preset 0 0 24 24 24 24 180 0 0 0 16777215 28 Save 16777215 28 Cancel Qt::Horizontal 20 20 6 0 0 26 26 Current Brush Engine 0 0 26 26 26 26 0 0 30 30 30 30 true Qt::Horizontal 40 20 Qt::Horizontal QSizePolicy::MinimumExpanding 0 0 0 0 0 16777215 28 Save New Brush Preset... 0 0 16777215 28 - Overwrite Brush + Overwrite Brush Preset Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 24 24 24 24 12 12 0 0 Presets 0 0 Engine: 0 0 0 0 0 0 0 120 1677215 1677215 Qt::Vertical QSizePolicy::Preferred 5 5 22 22 Qt::Horizontal 5 10 0 0 0 0 22 22 false false Qt::Vertical 20 10 false 0 3 1 3 3 0 4 100 0 QFrame::NoFrame QFrame::Plain 0 5 5 0 5 5 0 0 Erase mode will use a separate brush size Eraser switch size false 0 0 Erase mode will use a separate brush opacity Eraser switch opacity false 0 0 Temporarily Save Tweaks To Presets true Qt::Horizontal 40 20 60 0 5 5 0 5 0 false 5 5 5 5 5 24 24 24 20 12 12 Scratchpad QFrame::NoFrame QFrame::Plain 0 0 0 0 0 0 0 0 250 0 Fill area with brush preset icon Fill area with current image Fill area with gradient Fill area with background color Reset area to white KisScratchPad QWidget
kis_scratch_pad.h
1
KisPresetLivePreviewView QGraphicsView
kis_preset_live_preview_view.h
1
KisPresetSelectorStrip QWidget
kis_preset_selector_strip.h
1
KisLodAvailabilityWidget QWidget
kis_lod_availability_widget.h
1
diff --git a/libs/ui/forms/wdgsessionmanager.ui b/libs/ui/forms/wdgsessionmanager.ui index 18b0549761..75338cabc8 100644 --- a/libs/ui/forms/wdgsessionmanager.ui +++ b/libs/ui/forms/wdgsessionmanager.ui @@ -1,100 +1,100 @@ DlgSessionManager 0 0 363 238 Sessions 0 - + New... Rename... Switch to Delete... Qt::Vertical 20 40 Qt::Horizontal 40 20 Close diff --git a/libs/ui/kis_favorite_resource_manager.cpp b/libs/ui/kis_favorite_resource_manager.cpp index 6a6b7c87aa..a5da6b3ea8 100644 --- a/libs/ui/kis_favorite_resource_manager.cpp +++ b/libs/ui/kis_favorite_resource_manager.cpp @@ -1,368 +1,368 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2011 Sven Langkamp This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include "kis_favorite_resource_manager.h" #include "kis_popup_palette.h" #include "kis_paintop_box.h" #include "KisViewManager.h" #include "KisResourceServerProvider.h" #include "kis_min_heap.h" #include "kis_config.h" #include "kis_config_notifier.h" #include class KisFavoriteResourceManager::ColorDataList { public: static const int MAX_RECENT_COLOR = 12; ColorDataList() { m_key = 0; } ~ColorDataList() { qDeleteAll(m_guiList); } int size() { return m_guiList.size(); } int leastUsedGuiPos() { return findPos(m_priorityList.valueAt(0)); } const KoColor& guiColor(int pos) { Q_ASSERT_X(pos < size(), "ColorDataList::guiColor", "index out of bound"); Q_ASSERT_X(pos >= 0, "ColorDataList::guiColor", "negative index"); return m_guiList.at(pos)->data; } void append(const KoColor& data) { int pos = findPos(data); if (pos > -1) updateKey(pos); else appendNew(data); } void appendNew(const KoColor& data) { if (size() >= ColorDataList::MAX_RECENT_COLOR) removeLeastUsed(); PriorityNode * node; node = new PriorityNode (); node->data = data; node->key = m_key++; m_priorityList.append(node); int pos = guiInsertPos(data); pos >= m_guiList.size() ? m_guiList.append(node) : m_guiList.insert(pos, node); node = 0; } void removeLeastUsed() { Q_ASSERT_X(size() >= 0, "ColorDataList::removeLeastUsed", "index out of bound"); if (size() <= 0) return; int pos = findPos(m_priorityList.valueAt(0)); m_guiList.removeAt(pos); m_priorityList.remove(0); } void updateKey(int guiPos) { if (m_guiList.at(guiPos)->key == m_key - 1) return; m_priorityList.changeKey(m_guiList.at(guiPos)->pos, m_key++); } /*find position of the color on the gui list*/ int findPos(const KoColor& color) { int low = 0, high = size(), mid = 0; while (low < high) { mid = (low + high) / 2; if (hsvComparison(color, m_guiList.at(mid)->data) == 0) return mid; else if (hsvComparison(color, m_guiList.at(mid)->data) < 0) high = mid; else low = mid + 1; } return -1; } private: int m_key; int guiInsertPos(const KoColor& color) { int low = 0, high = size() - 1, mid = (low + high) / 2; while (low < high) { hsvComparison(color, m_guiList[mid]->data) == -1 ? high = mid : low = mid + 1; mid = (low + high) / 2; } if (m_guiList.size() > 0) { if (hsvComparison(color, m_guiList[mid]->data) == 1) ++mid; } return mid; } /*compares c1 and c2 based on HSV. c1 < c2, returns -1 c1 = c2, returns 0 c1 > c2, returns 1 */ int hsvComparison(const KoColor& c1, const KoColor& c2) { QColor qc1 = c1.toQColor(); QColor qc2 = c2.toQColor(); if (qc1.hue() < qc2.hue()) return -1; if (qc1.hue() > qc2.hue()) return 1; // hue is the same, ok let's compare saturation if (qc1.saturation() < qc2.saturation()) return -1; if (qc1.saturation() > qc2.saturation()) return 1; // oh, also saturation is same? if (qc1.value() < qc2.value()) return -1; if (qc1.value() > qc2.value()) return 1; // user selected two similar colors return 0; } KisMinHeap m_priorityList; QList *> m_guiList; }; KisFavoriteResourceManager::KisFavoriteResourceManager(KisPaintopBox *paintopBox) : m_paintopBox(paintopBox) , m_colorList(0) , m_initialized(false) { KisConfig cfg(true); m_maxPresets = cfg.favoritePresets(); m_colorList = new ColorDataList(); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(configChanged())); KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); rServer->addObserver(this); } KisFavoriteResourceManager::~KisFavoriteResourceManager() { KisPaintOpPresetResourceServer *rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); rServer->removeObserver(this); delete m_colorList; } void KisFavoriteResourceManager::unsetResourceServer() { // ... } QVector KisFavoriteResourceManager::favoritePresetNamesList() { init(); QVector names; for (int i = 0; i < m_maxPresets; i++) { QModelIndex index = m_resourcesProxyModel->index(i, 0); if (index.isValid()) { QString name = m_resourcesProxyModel->data(index, Qt::UserRole + KisResourceModel::Name).toString(); names << name; } else { break; // no more valid indeces } } return names; } QList KisFavoriteResourceManager::favoritePresetImages() { init(); QList images; for (int i = 0; i < m_maxPresets; i++) { QModelIndex index = m_resourcesProxyModel->index(i, 0); if (index.isValid()) { - QVariant tmp = m_resourcesProxyModel->data(index, Qt::UserRole + KisResourceModel::Image); + QVariant tmp = m_resourcesProxyModel->data(index, Qt::UserRole + KisResourceModel::Thumbnail); QImage image = tmp.value(); images << image; } else { break; // no more valid indeces } } return images; } void KisFavoriteResourceManager::setCurrentTag(const KisTagSP tag) { m_currentTag = tag; m_resourcesProxyModel->setTag(tag); KisConfig(false).writeEntry("favoritePresetsTag", tag->url()); updateFavoritePresets(); } void KisFavoriteResourceManager::slotChangeActivePaintop(int pos) { ENTER_FUNCTION() << ppVar(pos) << ppVar(numFavoritePresets()); if (pos < 0 || pos >= numFavoritePresets()) return; QModelIndex index = m_resourcesProxyModel->index(pos, 0); KoResourceSP resource = m_resourcesProxyModel->resourceForIndex(index); m_paintopBox->resourceSelected(resource); emit hidePalettes(); } int KisFavoriteResourceManager::numFavoritePresets() { init(); return favoritePresetNamesList().size(); } //Recent Colors void KisFavoriteResourceManager::slotUpdateRecentColor(int pos) { // Do not update the key, the colour might be selected but it is not used yet. So we are not supposed // to update the colour priority when we select it. m_colorList->updateKey(pos); emit setSelectedColor(pos); emit sigSetFGColor(m_colorList->guiColor(pos)); emit hidePalettes(); } void KisFavoriteResourceManager::slotAddRecentColor(const KoColor& color) { m_colorList->append(color); int pos = m_colorList->findPos(color); emit setSelectedColor(pos); } void KisFavoriteResourceManager::slotChangeFGColorSelector(KoColor c) { emit sigChangeFGColorSelector(c); } void KisFavoriteResourceManager::removingResource(QSharedPointer /*resource*/) { updateFavoritePresets(); } void KisFavoriteResourceManager::resourceAdded(QSharedPointer /*resource*/) { updateFavoritePresets(); } void KisFavoriteResourceManager::resourceChanged(QSharedPointer /*resource*/) { updateFavoritePresets(); } void KisFavoriteResourceManager::syncTaggedResourceView() { updateFavoritePresets(); } void KisFavoriteResourceManager::syncTagAddition(const QString& /*tag*/) {} void KisFavoriteResourceManager::syncTagRemoval(const QString& /*tag*/) {} int KisFavoriteResourceManager::recentColorsTotal() { return m_colorList->size(); } const KoColor& KisFavoriteResourceManager::recentColorAt(int pos) { return m_colorList->guiColor(pos); } void KisFavoriteResourceManager::slotSetBGColor(const KoColor c) { m_bgColor = c; } KoColor KisFavoriteResourceManager::bgColor() const { return m_bgColor; } bool sortPresetByName(KisPaintOpPresetSP preset1, KisPaintOpPresetSP preset2) { return preset1->name() < preset2->name(); } void KisFavoriteResourceManager::updateFavoritePresets() { emit updatePalettes(); } void KisFavoriteResourceManager::configChanged() { KisConfig cfg(true); m_maxPresets = cfg.favoritePresets(); updateFavoritePresets(); } void KisFavoriteResourceManager::init() { if (!m_initialized) { m_initialized = true; m_tagModel = KisTagModelProvider::tagModel(ResourceType::PaintOpPresets); m_resourcesProxyModel = new KisTagFilterResourceProxyModel(m_tagModel, this); m_resourcesProxyModel->setSourceModel(KisResourceModelProvider::resourceModel(ResourceType::PaintOpPresets)); m_resourceModel = KisResourceModelProvider::resourceModel(ResourceType::PaintOpPresets); KisResourceServerProvider::instance()->paintOpPresetServer(); QString currentTag = KisConfig(true).readEntry("favoritePresetsTag", "★ My Favorites"); // TODO: RESOURCES: tag by url? KisTagModel* tagModel = KisTagModelProvider::tagModel(ResourceType::PaintOpPresets); for (int i = 0; i < tagModel->rowCount(); i++) { QModelIndex index = tagModel->index(i, 0); KisTagSP tag = tagModel->tagForIndex(index); if (!tag.isNull() && tag->url() == currentTag) { m_currentTag = tag; break; } } m_resourcesProxyModel->setTag(m_currentTag); updateFavoritePresets(); } } diff --git a/libs/ui/kis_md5_generator.h b/libs/ui/kis_md5_generator.h deleted file mode 100644 index 6116a72497..0000000000 --- a/libs/ui/kis_md5_generator.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2015 Stefano Bonicatti - * - * 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 -#include "kritaui_export.h" -class KRITAUI_EXPORT KisMD5Generator : public KoMD5Generator -{ -public: - KisMD5Generator(); - ~KisMD5Generator() override; - - QByteArray generateHash(const QString &filename) override; - using KoMD5Generator::generateHash; -}; diff --git a/libs/ui/widgets/kis_paintop_presets_popup.cpp b/libs/ui/widgets/kis_paintop_presets_popup.cpp index f9453044dd..c74a69315b 100644 --- a/libs/ui/widgets/kis_paintop_presets_popup.cpp +++ b/libs/ui/widgets/kis_paintop_presets_popup.cpp @@ -1,847 +1,845 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "widgets/kis_paintop_presets_popup.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "KisResourceServerProvider.h" #include "kis_lod_availability_widget.h" #include "kis_signal_auto_connection.h" #include // ones from brush engine selector #include #include struct KisPaintOpPresetsPopup::Private { public: Ui_WdgPaintOpSettings uiWdgPaintOpPresetSettings; QGridLayout *layout; KisPaintOpConfigWidget *settingsWidget; QFont smallFont; KisCanvasResourceProvider *resourceProvider; KisFavoriteResourceManager *favoriteResManager; bool detached; bool ignoreHideEvents; bool isCreatingBrushFromScratch = false; QSize minimumSettingsWidgetSize; QRect detachedGeometry; KisSignalAutoConnectionsStore widgetConnections; }; KisPaintOpPresetsPopup::KisPaintOpPresetsPopup(KisCanvasResourceProvider * resourceProvider, KisFavoriteResourceManager* favoriteResourceManager, KisPresetSaveWidget* savePresetWidget, QWidget * parent) : QWidget(parent) , m_d(new Private()) { setObjectName("KisPaintOpPresetsPopup"); setFont(KoDockRegistry::dockFont()); current_paintOpId = ""; m_d->resourceProvider = resourceProvider; m_d->favoriteResManager = favoriteResourceManager; m_d->uiWdgPaintOpPresetSettings.setupUi(this); m_d->layout = new QGridLayout(m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer); m_d->layout->setSizeConstraint(QLayout::SetFixedSize); m_d->uiWdgPaintOpPresetSettings.scratchPad->setupScratchPad(resourceProvider, Qt::white); m_d->uiWdgPaintOpPresetSettings.scratchPad->setCutoutOverlayRect(QRect(25, 25, 200, 200)); m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setToolTip(i18n("The settings for this preset have changed from their default.")); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setToolTip(i18n("Toggle showing presets")); m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setToolTip(i18n("Toggle showing scratchpad")); m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setToolTip(i18n("Reload the brush preset")); m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setToolTip(i18n("Rename the brush preset")); // creating a new preset from scratch. Part of the brush presets area // the menu options will get filled up later when we are generating all available paintops // in the filter drop-down newPresetBrushEnginesMenu = new QMenu(); // overwrite existing preset and saving a new preset use the same dialog saveDialog = savePresetWidget; saveDialog->scratchPadSetup(resourceProvider); saveDialog->setFavoriteResourceManager(m_d->favoriteResManager); // this is needed when saving the preset saveDialog->hide(); // the area on the brush editor for renaming the brush. make sure edit fields are hidden by default toggleBrushRenameUIActive(false); // DETAIL and THUMBNAIL view changer QMenu* menu = new QMenu(this); menu->setStyleSheet("margin: 6px"); menu->addSection(i18nc("@title Which elements to display (e.g., thumbnails or details)", "Display")); QActionGroup *actionGroup = new QActionGroup(this); KisPresetChooser::ViewMode mode = (KisPresetChooser::ViewMode)KisConfig(true).presetChooserViewMode(); QAction* action = menu->addAction(KisIconUtils::loadIcon("view-preview"), i18n("Thumbnails"), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotThumbnailMode())); action->setCheckable(true); action->setChecked(mode == KisPresetChooser::THUMBNAIL); action->setActionGroup(actionGroup); action = menu->addAction(KisIconUtils::loadIcon("view-list-details"), i18n("Details"), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotDetailMode())); action->setCheckable(true); action->setChecked(mode == KisPresetChooser::DETAIL); action->setActionGroup(actionGroup); // add horizontal slider for the icon size QSlider* iconSizeSlider = new QSlider(this); iconSizeSlider->setOrientation(Qt::Horizontal); iconSizeSlider->setRange(30, 80); iconSizeSlider->setValue(m_d->uiWdgPaintOpPresetSettings.presetWidget->iconSize()); iconSizeSlider->setMinimumHeight(20); iconSizeSlider->setMinimumWidth(40); iconSizeSlider->setTickInterval(10); QWidgetAction *sliderAction= new QWidgetAction(this); sliderAction->setDefaultWidget(iconSizeSlider); menu->addSection(i18n("Icon Size")); menu->addAction(sliderAction); // configure the button and assign menu m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setMenu(menu); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setPopupMode(QToolButton::InstantPopup); // loading preset from scratch option m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setPopupMode(QToolButton::InstantPopup); // show/hide buttons KisConfig cfg(true); m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setChecked(cfg.scratchpadVisible()); if (cfg.scratchpadVisible()) { slotSwitchScratchpad(true); // show scratchpad } else { slotSwitchScratchpad(false); } m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setCheckable(true); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setChecked(false); slotSwitchShowPresets(false); // hide presets by default // Connections connect(m_d->uiWdgPaintOpPresetSettings.paintPresetIcon, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(paintPresetImage())); connect(saveDialog, SIGNAL(resourceSelected(KoResourceSP )), this, SLOT(resourceSelected(KoResourceSP ))); connect (m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton, SIGNAL(clicked(bool)), this, SLOT(slotRenameBrushActivated())); connect (m_d->uiWdgPaintOpPresetSettings.cancelBrushNameUpdateButton, SIGNAL(clicked(bool)), this, SLOT(slotRenameBrushDeactivated())); connect(m_d->uiWdgPaintOpPresetSettings.updateBrushNameButton, SIGNAL(clicked(bool)), this, SLOT(slotSaveRenameCurrentBrush())); connect(m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField, SIGNAL(returnPressed()), SLOT(slotSaveRenameCurrentBrush())); connect(iconSizeSlider, SIGNAL(sliderMoved(int)), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSetIconSize(int))); connect(iconSizeSlider, SIGNAL(sliderReleased()), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSaveIconSize())); connect(m_d->uiWdgPaintOpPresetSettings.showScratchpadButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchScratchpad(bool))); connect(m_d->uiWdgPaintOpPresetSettings.showPresetsButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchShowPresets(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraseScratchPad, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillDefault())); connect(m_d->uiWdgPaintOpPresetSettings.fillLayer, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillLayer())); connect(m_d->uiWdgPaintOpPresetSettings.fillGradient, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillGradient())); connect(m_d->uiWdgPaintOpPresetSettings.fillSolid, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillBackground())); m_d->settingsWidget = 0; setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); connect(m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton, SIGNAL(clicked()), this, SLOT(slotSaveBrushPreset())); connect(m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton, SIGNAL(clicked()), this, SLOT(slotSaveNewBrushPreset())); connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()), this, SIGNAL(reloadPresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(dirtyPresetToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(eraserBrushSizeToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(eraserBrushOpacityToggled(bool))); // preset widget connections connect(m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SIGNAL(resourceSelected(KoResourceSP )), this, SIGNAL(signalResourceSelected(KoResourceSP ))); connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SLOT(updateViewSettings())); connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()), SLOT(slotUpdatePresetSettings())); m_d->detached = false; m_d->ignoreHideEvents = false; m_d->minimumSettingsWidgetSize = QSize(0, 0); m_d->detachedGeometry = QRect(100, 100, 0, 0); m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox->setChecked(cfg.useDirtyPresets()); m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox->setChecked(cfg.useEraserBrushSize()); m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox->setChecked(cfg.useEraserBrushOpacity()); m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setCanvasResourceManager(resourceProvider->resourceManager()); connect(resourceProvider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), SLOT(slotResourceChanged(int,QVariant))); connect(m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability, SIGNAL(sigUserChangedLodAvailability(bool)), SLOT(slotLodAvailabilityChanged(bool))); connect(m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability, SIGNAL(sigUserChangedLodThreshold(qreal)), SLOT(slotLodThresholdChanged(qreal))); slotResourceChanged(KisCanvasResourceProvider::LodAvailability, resourceProvider->resourceManager()-> resource(KisCanvasResourceProvider::LodAvailability)); slotResourceChanged(KisCanvasResourceProvider::LodSizeThreshold, resourceProvider->resourceManager()-> resource(KisCanvasResourceProvider::LodSizeThreshold)); connect(m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdatePaintOpFilter())); connect(m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset, SIGNAL(clicked()), this, SLOT(slotBlackListCurrentPreset())); updateThemedIcons(); // setup things like the scene construct images, layers, etc that is a one-time thing m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->setup(); } void KisPaintOpPresetsPopup::slotBlackListCurrentPreset() { KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); KisPaintOpPresetSP curPreset = m_d->resourceProvider->currentPreset(); if (rServer->resourceByName(curPreset->name())) { rServer->removeResourceFromServer(curPreset); } } void KisPaintOpPresetsPopup::slotRenameBrushActivated() { toggleBrushRenameUIActive(true); } void KisPaintOpPresetsPopup::slotRenameBrushDeactivated() { toggleBrushRenameUIActive(false); } void KisPaintOpPresetsPopup::toggleBrushRenameUIActive(bool isRenaming) { // This function doesn't really do anything except get the UI in a state to rename a brush preset m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setVisible(isRenaming); m_d->uiWdgPaintOpPresetSettings.updateBrushNameButton->setVisible(isRenaming); m_d->uiWdgPaintOpPresetSettings.cancelBrushNameUpdateButton->setVisible(isRenaming); // hide these below areas while renaming m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setVisible(!isRenaming); m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setVisible(!isRenaming); m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setEnabled(!isRenaming); m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setVisible(!isRenaming); m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton->setEnabled(!isRenaming); m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton->setVisible(!isRenaming); // if the presets area is shown, only then can you show/hide the load default brush // need to think about weird state when you are in the middle of renaming a brush // what happens if you try to change presets. maybe we should auto-hide (or disable) // the presets area in this case if (m_d->uiWdgPaintOpPresetSettings.presetWidget->isVisible()) { m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setVisible(!isRenaming); m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset->setVisible(!isRenaming); } } void KisPaintOpPresetsPopup::slotSaveRenameCurrentBrush() { // if you are renaming a brush, that is different than updating the settings // make sure we are in a clean state before renaming. This logic might change, // but that is what we are going with for now emit reloadPresetClicked(); // get a reference to the existing (and new) file name and path that we are working with KisPaintOpPresetSP curPreset = m_d->resourceProvider->currentPreset(); if (!curPreset) return; KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); - QString saveLocation = rServer->saveLocation(); QString originalPresetName = curPreset->name(); QString renamedPresetName = m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->text(); - QString originalPresetPathAndFile = saveLocation + originalPresetName + curPreset->defaultFileExtension(); - QString renamedPresetPathAndFile = saveLocation + renamedPresetName + curPreset->defaultFileExtension(); + QString renamedPresetPathAndFile = renamedPresetName + curPreset->defaultFileExtension(); // create a new brush preset with the name specified and add to resource provider KisPaintOpPresetSP newPreset = curPreset->clone().dynamicCast(); newPreset->setFilename(renamedPresetPathAndFile); // this also contains the path newPreset->setName(renamedPresetName); newPreset->setImage(curPreset->image()); // use existing thumbnail (might not need to do this) newPreset->setDirty(false); newPreset->setValid(true); rServer->addResource(newPreset); resourceSelected(newPreset); // refresh and select our freshly renamed resource // Now blacklist the original file if (rServer->resourceByName(originalPresetName)) { rServer->removeResourceFromServer(curPreset); } m_d->favoriteResManager->updateFavoritePresets(); toggleBrushRenameUIActive(false); // this returns the UI to its original state after saving slotUpdatePresetSettings(); // update visibility of dirty preset and icon } void KisPaintOpPresetsPopup::slotResourceChanged(int key, const QVariant &value) { if (key == KisCanvasResourceProvider::LodAvailability) { m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->slotUserChangedLodAvailability(value.toBool()); } else if (key == KisCanvasResourceProvider::LodSizeThreshold) { m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->slotUserChangedLodThreshold(value.toDouble()); } else if (key == KisCanvasResourceProvider::Size) { m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->slotUserChangedSize(value.toDouble()); } } void KisPaintOpPresetsPopup::slotLodAvailabilityChanged(bool value) { m_d->resourceProvider->resourceManager()->setResource(KisCanvasResourceProvider::LodAvailability, QVariant(value)); } void KisPaintOpPresetsPopup::slotLodThresholdChanged(qreal value) { m_d->resourceProvider->resourceManager()->setResource(KisCanvasResourceProvider::LodSizeThreshold, QVariant(value)); } KisPaintOpPresetsPopup::~KisPaintOpPresetsPopup() { if (m_d->settingsWidget) { m_d->layout->removeWidget(m_d->settingsWidget); m_d->settingsWidget->hide(); m_d->settingsWidget->setParent(0); m_d->settingsWidget = 0; } delete m_d; delete newPresetBrushEnginesMenu; } void KisPaintOpPresetsPopup::setPaintOpSettingsWidget(QWidget * widget) { if (m_d->settingsWidget) { m_d->layout->removeWidget(m_d->settingsWidget); m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer->updateGeometry(); } m_d->layout->update(); updateGeometry(); m_d->widgetConnections.clear(); m_d->settingsWidget = 0; if (widget) { m_d->settingsWidget = dynamic_cast(widget); KIS_ASSERT_RECOVER_RETURN(m_d->settingsWidget); KisConfig cfg(true); if (m_d->settingsWidget->supportScratchBox() && cfg.scratchpadVisible()) { slotSwitchScratchpad(true); } else { slotSwitchScratchpad(false); } m_d->widgetConnections.addConnection(m_d->settingsWidget, SIGNAL(sigConfigurationItemChanged()), this, SLOT(slotUpdateLodAvailability())); widget->setFont(m_d->smallFont); QSize hint = widget->sizeHint(); m_d->minimumSettingsWidgetSize = QSize(qMax(hint.width(), m_d->minimumSettingsWidgetSize.width()), qMax(hint.height(), m_d->minimumSettingsWidgetSize.height())); widget->setMinimumSize(m_d->minimumSettingsWidgetSize); m_d->layout->addWidget(widget); // hook up connections that will monitor if our preset is dirty or not. Show a notification if it is if (m_d->resourceProvider && m_d->resourceProvider->currentPreset() ) { KisPaintOpPresetSP preset = m_d->resourceProvider->currentPreset(); m_d->widgetConnections.addConnection(preset->updateProxy(), SIGNAL(sigSettingsChanged()), this, SLOT(slotUpdatePresetSettings())); } m_d->layout->update(); widget->show(); } slotUpdateLodAvailability(); } void KisPaintOpPresetsPopup::slotUpdateLodAvailability() { if (!m_d->settingsWidget) return; KisPaintopLodLimitations l = m_d->settingsWidget->lodLimitations(); m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setLimitations(l); } QImage KisPaintOpPresetsPopup::cutOutOverlay() { return m_d->uiWdgPaintOpPresetSettings.scratchPad->cutoutOverlay(); } void KisPaintOpPresetsPopup::contextMenuEvent(QContextMenuEvent *e) { Q_UNUSED(e); } void KisPaintOpPresetsPopup::switchDetached(bool show) { if (parentWidget()) { m_d->detached = !m_d->detached; if (m_d->detached) { m_d->ignoreHideEvents = true; if (show) { parentWidget()->show(); } m_d->ignoreHideEvents = false; } else { parentWidget()->hide(); } KisConfig cfg(false); cfg.setPaintopPopupDetached(m_d->detached); } } void KisPaintOpPresetsPopup::setCreatingBrushFromScratch(bool enabled) { m_d->isCreatingBrushFromScratch = enabled; } void KisPaintOpPresetsPopup::resourceSelected(KoResourceSP resource) { // this gets called every time the brush editor window is opened // TODO: this gets called multiple times whenever the preset is changed in the presets area // the connections probably need to be thought about with this a bit more to keep things in sync m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(resource); // find the display name of the brush engine and append it to the selected preset display QString currentBrushEngineName; QPixmap currentBrushEngineIcon = QPixmap(26, 26); currentBrushEngineIcon.fill(Qt::transparent); for(int i=0; i < sortedBrushEnginesList.length(); i++) { if (sortedBrushEnginesList.at(i).id == currentPaintOpId() ) { currentBrushEngineName = sortedBrushEnginesList.at(i).name; currentBrushEngineIcon = sortedBrushEnginesList.at(i).icon.pixmap(26, 26); } } // brush names have underscores as part of the file name (to help with building). We don't really need underscores // when viewing the names, so replace them with spaces QString formattedBrushName = resource->name().replace("_", " "); m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setText(formattedBrushName); m_d->uiWdgPaintOpPresetSettings.currentBrushEngineLabel->setText(i18nc("%1 is the name of a brush engine", "%1 Engine", currentBrushEngineName)); m_d->uiWdgPaintOpPresetSettings.currentBrushEngineIcon->setPixmap(currentBrushEngineIcon); m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setText(resource->name()); // use file name // get the preset image and pop it into the thumbnail area on the top of the brush editor m_d->uiWdgPaintOpPresetSettings.presetThumbnailicon->setPixmap(QPixmap::fromImage(resource->image().scaled(55, 55, Qt::KeepAspectRatio, Qt::SmoothTransformation))); toggleBrushRenameUIActive(false); // reset the UI state of renaming a brush if we are changing brush presets slotUpdatePresetSettings(); // check to see if the dirty preset icon needs to be shown } bool variantLessThan(const KisPaintOpInfo v1, const KisPaintOpInfo v2) { return v1.priority < v2.priority; } void KisPaintOpPresetsPopup::setPaintOpList(const QList< KisPaintOpFactory* >& list) { m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->clear(); // reset combobox list just in case // create a new list so we can sort it and populate the brush engine combo box sortedBrushEnginesList.clear(); // just in case this function is called again, don't keep adding to the list for(int i=0; i < list.length(); i++) { KisPaintOpInfo paintOpInfo; paintOpInfo.id = list.at(i)->id(); paintOpInfo.name = list.at(i)->name(); paintOpInfo.icon = list.at(i)->icon(); paintOpInfo.priority = list.at(i)->priority(); sortedBrushEnginesList.append(paintOpInfo); } std::stable_sort(sortedBrushEnginesList.begin(), sortedBrushEnginesList.end(), variantLessThan ); // add an "All" option at the front to show all presets QPixmap emptyPixmap = QPixmap(22,22); emptyPixmap.fill(Qt::transparent); // if we create a new brush from scratch, we need a full list of paintops to choose from // we don't want "All", so populate the list before that is added newPresetBrushEnginesMenu->actions().clear(); // clean out list in case we run this again newBrushEngineOptions.clear(); for (int j = 0; j < sortedBrushEnginesList.length(); j++) { KisAction * newEngineAction = static_cast( newPresetBrushEnginesMenu->addAction(sortedBrushEnginesList[j].name)); newEngineAction->setObjectName(sortedBrushEnginesList[j].id); // we need the ID for changing the paintop when action triggered newEngineAction->setIcon(sortedBrushEnginesList[j].icon); newBrushEngineOptions.append(newEngineAction); connect(newEngineAction, SIGNAL(triggered()), this, SLOT(slotCreateNewBrushPresetEngine())); } m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setMenu(newPresetBrushEnginesMenu); // fill the list into the brush combo box sortedBrushEnginesList.push_front(KisPaintOpInfo(QString("all_options"), i18n("All"), QString(""), QIcon(emptyPixmap), 0 )); for (int m = 0; m < sortedBrushEnginesList.length(); m++) { m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->addItem(sortedBrushEnginesList[m].icon, sortedBrushEnginesList[m].name, QVariant(sortedBrushEnginesList[m].id)); } } void KisPaintOpPresetsPopup::setCurrentPaintOpId(const QString& paintOpId) { current_paintOpId = paintOpId; } QString KisPaintOpPresetsPopup::currentPaintOpId() { return current_paintOpId; } void KisPaintOpPresetsPopup::setPresetImage(const QImage& image) { m_d->uiWdgPaintOpPresetSettings.scratchPad->setPresetImage(image); saveDialog->brushPresetThumbnailWidget->setPresetImage(image); } void KisPaintOpPresetsPopup::hideEvent(QHideEvent *event) { if (m_d->ignoreHideEvents) { return; } if (m_d->detached) { m_d->detachedGeometry = window()->geometry(); } QWidget::hideEvent(event); } void KisPaintOpPresetsPopup::showEvent(QShowEvent *) { if (m_d->detached) { window()->setGeometry(m_d->detachedGeometry); } emit brushEditorShown(); } void KisPaintOpPresetsPopup::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); if (parentWidget()) { // Make sure resizing doesn't push this widget out of the screen QRect screenRect = QApplication::desktop()->availableGeometry(this); QRect newPositionRect = kisEnsureInRect(parentWidget()->geometry(), screenRect); parentWidget()->setGeometry(newPositionRect); } } bool KisPaintOpPresetsPopup::detached() const { return m_d->detached; } void KisPaintOpPresetsPopup::slotSwitchScratchpad(bool visible) { // hide all the internal controls except the toggle button m_d->uiWdgPaintOpPresetSettings.scratchPad->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.fillGradient->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.fillLayer->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.fillSolid->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.scratchpadSidebarLabel->setVisible(visible); if (visible) { m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setIcon(KisIconUtils::loadIcon("arrow-left")); } else { m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setIcon(KisIconUtils::loadIcon("arrow-right")); } KisConfig cfg(false); cfg.setScratchpadVisible(visible); } void KisPaintOpPresetsPopup::slotSwitchShowEditor(bool visible) { m_d->uiWdgPaintOpPresetSettings.brushEditorSettingsControls->setVisible(visible); } void KisPaintOpPresetsPopup::slotSwitchShowPresets(bool visible) { m_d->uiWdgPaintOpPresetSettings.presetWidget->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.engineFilterLabel->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.presetsSidebarLabel->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setVisible(visible); m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset->setVisible(visible); // we only want a spacer to work when the toggle icon is present. Otherwise the list of presets will shrink // which is something we don't want if (visible) { m_d->uiWdgPaintOpPresetSettings.presetsSpacer->changeSize(0,0, QSizePolicy::Ignored,QSizePolicy::Ignored); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("arrow-right")); } else { m_d->uiWdgPaintOpPresetSettings.presetsSpacer->changeSize(0,0, QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("arrow-left")); } } void KisPaintOpPresetsPopup::slotUpdatePaintOpFilter() { QVariant userData = m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->currentData(); // grab paintOpID from data QString filterPaintOpId = userData.toString(); if (filterPaintOpId == "all_options") { filterPaintOpId = ""; } m_d->uiWdgPaintOpPresetSettings.presetWidget->setPresetFilter(filterPaintOpId); } void KisPaintOpPresetsPopup::slotSaveBrushPreset() { // here we are assuming that people want to keep their existing preset icon. We will just update the // settings and save a new copy with the same name. // there is a dialog with save options, but we don't need to show it in this situation saveDialog->useNewBrushDialog(false); // this mostly just makes sure we keep the existing brush preset name when saving saveDialog->loadExistingThumbnail(); // This makes sure we use the existing preset icon when updating the existing brush preset saveDialog->savePreset(); // refresh the view settings so the brush doesn't appear dirty slotUpdatePresetSettings(); } void KisPaintOpPresetsPopup::slotSaveNewBrushPreset() { saveDialog->useNewBrushDialog(true); saveDialog->saveScratchPadThumbnailArea(m_d->uiWdgPaintOpPresetSettings.scratchPad->cutoutOverlay()); saveDialog->showDialog(); } void KisPaintOpPresetsPopup::slotCreateNewBrushPresetEngine() { emit createPresetFromScratch(sender()->objectName()); } void KisPaintOpPresetsPopup::updateViewSettings() { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->updateViewSettings(); } void KisPaintOpPresetsPopup::currentPresetChanged(KisPaintOpPresetSP preset) { if (preset) { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(preset); setCurrentPaintOpId(preset->paintOp().id()); } } void KisPaintOpPresetsPopup::updateThemedIcons() { m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); m_d->uiWdgPaintOpPresetSettings.fillLayer->setIcon(KisIconUtils::loadIcon("document-new")); m_d->uiWdgPaintOpPresetSettings.fillLayer->hide(); m_d->uiWdgPaintOpPresetSettings.fillGradient->setIcon(KisIconUtils::loadIcon("krita_tool_gradient")); m_d->uiWdgPaintOpPresetSettings.fillSolid->setIcon(KisIconUtils::loadIcon("krita_tool_color_fill")); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setIcon(KisIconUtils::loadIcon("edit-delete")); m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setIcon(KisIconUtils::loadIcon("addlayer")); m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset->setIcon(KisIconUtils::loadIcon("deletelayer")); m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setIcon(KisIconUtils::loadIcon("updateColorize")); // refresh icon m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setIcon(KisIconUtils::loadIcon("dirty-preset")); // edit icon m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setIcon(KisIconUtils::loadIcon("warning")); m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setIcon(KisIconUtils::loadIcon("addlayer")); m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset->setIcon(KisIconUtils::loadIcon("deletelayer")); m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("configure")); // if we cannot see the "Preset label", we know it is not visible // maybe this can also be stored in the config like the scratchpad? if (m_d->uiWdgPaintOpPresetSettings.presetsSidebarLabel->isVisible()) { m_d->uiWdgPaintOpPresetSettings.presetsSpacer->changeSize(0,0, QSizePolicy::Ignored,QSizePolicy::Ignored); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("arrow-right")); } else { m_d->uiWdgPaintOpPresetSettings.presetsSpacer->changeSize(0,0, QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding); m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("arrow-left")); } // we store whether the scratchpad if visible in the config. KisConfig cfg(true); if (cfg.scratchpadVisible()) { m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setIcon(KisIconUtils::loadIcon("arrow-left")); } else { m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setIcon(KisIconUtils::loadIcon("arrow-right")); } } void KisPaintOpPresetsPopup::slotUpdatePresetSettings() { if (!m_d->resourceProvider) { return; } if (!m_d->resourceProvider->currentPreset()) { return; } // hide options on UI if we are creating a brush preset from scratch to prevent confusion if (m_d->isCreatingBrushFromScratch) { m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setVisible(false); m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setVisible(false); m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setVisible(false); m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setVisible(false); } else { bool isPresetDirty = m_d->resourceProvider->currentPreset()->isDirty(); // don't need to reload or overwrite a clean preset m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setVisible(isPresetDirty); m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setVisible(isPresetDirty); m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setEnabled(isPresetDirty); m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setVisible(true); } // update live preview area in here... // don't update the live preview if the widget is not visible. if (m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->isVisible()) { m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->setCurrentPreset(m_d->resourceProvider->currentPreset()); m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->requestUpdateStroke(); } } diff --git a/libs/ui/widgets/kis_paintop_presets_save.cpp b/libs/ui/widgets/kis_paintop_presets_save.cpp index 1ed8bfbb25..907d00a3e9 100644 --- a/libs/ui/widgets/kis_paintop_presets_save.cpp +++ b/libs/ui/widgets/kis_paintop_presets_save.cpp @@ -1,223 +1,222 @@ /* This file is part of the KDE project * Copyright (C) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "widgets/kis_paintop_presets_save.h" #include #include #include #include #include #include #include "KisImportExportManager.h" #include "QDesktopServices" #include "KisResourceServerProvider.h" #include KisPresetSaveWidget::KisPresetSaveWidget(QWidget * parent) : KisPaintOpPresetSaveDialog(parent) { // this is setting the area we will "capture" for saving the brush preset. It can potentially be a different // area that the entire scratchpad brushPresetThumbnailWidget->setCutoutOverlayRect(QRect(0, 0, brushPresetThumbnailWidget->height(), brushPresetThumbnailWidget->width())); // we will default to reusing the previous preset thumbnail // have that checked by default, hide the other elements, and load the last preset image connect(clearBrushPresetThumbnailButton, SIGNAL(clicked(bool)), brushPresetThumbnailWidget, SLOT(fillDefault())); connect(loadImageIntoThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadImageFromFile())); connect(loadScratchPadThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadScratchpadThumbnail())); connect(loadExistingThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadExistingThumbnail())); connect(loadIconLibraryThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadImageFromLibrary())); connect(savePresetButton, SIGNAL(clicked(bool)), this, SLOT(savePreset())); connect(cancelButton, SIGNAL(clicked(bool)), this, SLOT(close())); } KisPresetSaveWidget::~KisPresetSaveWidget() { } void KisPresetSaveWidget::scratchPadSetup(KisCanvasResourceProvider* resourceProvider) { m_resourceProvider = resourceProvider; brushPresetThumbnailWidget->setupScratchPad(m_resourceProvider, Qt::white); } void KisPresetSaveWidget::showDialog() { setModal(true); // set the name of the current brush preset area. KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); // UI will look a bit different if we are saving a new brush if (m_useNewBrushDialog) { setWindowTitle(i18n("Save New Brush Preset")); newBrushNameTexField->setVisible(true); clearBrushPresetThumbnailButton->setVisible(true); loadImageIntoThumbnailButton->setVisible(true); currentBrushNameLabel->setVisible(false); if (preset) { newBrushNameTexField->setText(preset->name().append(" ").append(i18n("Copy"))); } } else { setWindowTitle(i18n("Save Brush Preset")); if (preset) { currentBrushNameLabel->setText(preset->name()); } newBrushNameTexField->setVisible(false); currentBrushNameLabel->setVisible(true); } brushPresetThumbnailWidget->paintPresetImage(); show(); } void KisPresetSaveWidget::loadImageFromFile() { // create a dialog to retrieve an image file. KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); QString filename = dialog.filename(); // the filename() returns the entire path & file name, not just the file name if (filename != "") { // empty if "cancel" is pressed // take that file and load it into the thumbnail are const QImage imageToLoad(filename); brushPresetThumbnailWidget->fillTransparent(); // clear the background in case our new image has transparency brushPresetThumbnailWidget->paintCustomImage(imageToLoad); } } void KisPresetSaveWidget::loadScratchpadThumbnail() { brushPresetThumbnailWidget->paintCustomImage(scratchPadThumbnailArea); } void KisPresetSaveWidget::loadExistingThumbnail() { brushPresetThumbnailWidget->paintPresetImage(); } void KisPresetSaveWidget::loadImageFromLibrary() { //add dialog code here. QDialog *dlg = new QDialog(this); dlg->setWindowTitle(i18n("Preset Icon Library")); QVBoxLayout *layout = new QVBoxLayout(); dlg->setLayout(layout); KisPaintopPresetIconLibrary *libWidget = new KisPaintopPresetIconLibrary(dlg); layout->addWidget(libWidget); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, dlg); connect(buttons, SIGNAL(accepted()), dlg, SLOT(accept())); connect(buttons, SIGNAL(rejected()), dlg, SLOT(reject())); layout->addWidget(buttons); //if dialog accepted, get image. if (dlg->exec()==QDialog::Accepted) { QImage presetImage = libWidget->getImage(); brushPresetThumbnailWidget->paintCustomImage(presetImage); } } void KisPresetSaveWidget::setFavoriteResourceManager(KisFavoriteResourceManager * favManager) { m_favoriteResourceManager = favManager; } void KisPresetSaveWidget::savePreset() { KisPaintOpPresetSP curPreset = m_resourceProvider->currentPreset(); - if (!curPreset) + if (!curPreset) { return; + } KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); QString saveLocation = rServer->saveLocation(); // if we are saving a new brush, use what we type in for the input - QString presetName = m_useNewBrushDialog ? newBrushNameTexField->text() : curPreset->name(); - QString currentPresetFileName = saveLocation + presetName.replace(" ", "_") + curPreset->defaultFileExtension(); + QString presetFileName = m_useNewBrushDialog ? newBrushNameTexField->text() : curPreset->name(); + // We don't want dots or spaces in the filenames + presetFileName = presetFileName.replace(' ', '_').replace('.', '_'); if (m_useNewBrushDialog) { KisPaintOpPresetSP newPreset = curPreset->clone().dynamicCast(); - newPreset->setFilename(currentPresetFileName.split("/").last()); - newPreset->setName(presetName); + newPreset->setFilename(presetFileName); + newPreset->setName(m_useNewBrushDialog ? newBrushNameTexField->text() : curPreset->name()); newPreset->setImage(brushPresetThumbnailWidget->cutoutOverlay()); newPreset->setDirty(false); newPreset->setValid(true); rServer->addResource(newPreset); // trying to get brush preset to load after it is created emit resourceSelected(newPreset); } else { // saving a preset that is replacing an existing one - - curPreset->setFilename(currentPresetFileName.split("/").last()); - curPreset->setName(presetName); - + curPreset->setName(m_useNewBrushDialog ? newBrushNameTexField->text() : curPreset->name()); if (curPreset->image().isNull()) { curPreset->setImage(brushPresetThumbnailWidget->cutoutOverlay()); } - rServer->addResource(curPreset); + rServer->updateResource(curPreset); } // // HACK ALERT! the server does not notify the observers // // automatically, so we need to call theupdate manually! // rServer->tagCategoryMembersChanged(); m_favoriteResourceManager->updateFavoritePresets(); close(); // we are done... so close the save brush dialog } void KisPresetSaveWidget::saveScratchPadThumbnailArea(QImage image) { scratchPadThumbnailArea = image; } void KisPresetSaveWidget::useNewBrushDialog(bool show) { m_useNewBrushDialog = show; } #include "moc_kis_paintop_presets_save.cpp" diff --git a/libs/ui/widgets/kis_preset_chooser.cpp b/libs/ui/widgets/kis_preset_chooser.cpp index 241087ab6c..5affde2aa3 100644 --- a/libs/ui/widgets/kis_preset_chooser.cpp +++ b/libs/ui/widgets/kis_preset_chooser.cpp @@ -1,450 +1,459 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2009 Sven Langkamp * Copyright (C) 2011 Silvio Heinrich * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_preset_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceServerProvider.h" #include "kis_global.h" #include "kis_slider_spin_box.h" #include "kis_config_notifier.h" #include /// The resource item delegate for rendering the resource preview class KisPresetDelegate : public QAbstractItemDelegate { public: KisPresetDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent), m_showText(false), m_useDirtyPresets(false) {} ~KisPresetDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } void setShowText(bool showText) { m_showText = showText; } void setUseDirtyPresets(bool value) { m_useDirtyPresets = value; } private: bool m_showText; bool m_useDirtyPresets; }; void KisPresetDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { painter->save(); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); if (!index.isValid()) { qDebug() << "KisPresetDelegate::paint: index is invalid"; painter->restore(); return; } bool dirty = index.data(Qt::UserRole + KisResourceModel::Dirty).toBool(); - QImage preview = index.data(Qt::UserRole + KisResourceModel::Image).value(); + QImage preview = index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); if (preview.isNull()) { qDebug() << "KisPresetDelegate::paint: Preview is null"; painter->restore(); return; } QMap metaData = index.data(Qt::UserRole + KisResourceModel::MetaData).value>(); QRect paintRect = option.rect.adjusted(1, 1, -1, -1); if (!m_showText) { painter->drawImage(paintRect.x(), paintRect.y(), preview.scaled(paintRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else { QSize pixSize(paintRect.height(), paintRect.height()); painter->drawImage(paintRect.x(), paintRect.y(), preview.scaled(pixSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); // Put an asterisk after the preset if it is dirty. This will help in case the pixmap icon is too small QString dirtyPresetIndicator = QString(""); if (m_useDirtyPresets && dirty) { dirtyPresetIndicator = QString("*"); } // qreal brushSize = metaData["paintopSize"].toReal(); // QString brushSizeText; // // Disable displayed decimal precision beyond a certain brush size // if (brushSize < 100) { // brushSizeText = QString::number(brushSize, 'g', 3); // } else { // brushSizeText = QString::number(brushSize, 'f', 0); // } // painter->drawText(pixSize.width() + 10, option.rect.y() + option.rect.height() - 10, brushSizeText); // brush size QString presetDisplayName = index.data(Qt::UserRole + KisResourceModel::Name).toString().replace("_", " "); // don't need underscores that might be part of the file name painter->drawText(pixSize.width() + 40, option.rect.y() + option.rect.height() - 10, presetDisplayName.append(dirtyPresetIndicator)); } if (m_useDirtyPresets && dirty) { const QIcon icon = KisIconUtils::loadIcon(koIconName("dirty-preset")); QPixmap pixmap = icon.pixmap(QSize(15,15)); painter->drawPixmap(paintRect.x() + 3, paintRect.y() + 3, pixmap); } // if (!preset->settings() || !preset->settings()->isValid()) { // const QIcon icon = KisIconUtils::loadIcon("broken-preset"); // icon.paint(painter, QRect(paintRect.x() + paintRect.height() - 25, paintRect.y() + paintRect.height() - 25, 25, 25)); // } if (option.state & QStyle::State_Selected) { painter->setCompositionMode(QPainter::CompositionMode_HardLight); painter->setOpacity(1.0); painter->fillRect(option.rect, option.palette.highlight()); // highlight is not strong enough to pick out preset. draw border around it. painter->setCompositionMode(QPainter::CompositionMode_SourceOver); painter->setPen(QPen(option.palette.highlight(), 4, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); QRect selectedBorder = option.rect.adjusted(2 , 2, -2, -2); // constrict the rectangle so it doesn't bleed into other presets painter->drawRect(selectedBorder); } painter->restore(); } class KisPresetChooser::PaintOpFilterModel : public QSortFilterProxyModel, public KisAbstractResourceModel { Q_OBJECT public: PaintOpFilterModel(QObject *parent = 0) : QSortFilterProxyModel(parent) { } ~PaintOpFilterModel() override { } void setPaintOpId(const QString &id) { m_id = id; } QString currentPaintOpId() const { return m_id; } // KisAbstractResourceModel interface public: KoResourceSP resourceForIndex(QModelIndex index) const override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->resourceForIndex(mapToSource(index)); } return 0; } QModelIndex indexFromResource(KoResourceSP resource) const override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return mapFromSource(source->indexFromResource(resource)); } return QModelIndex(); } bool removeResource(const QModelIndex &index) override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->removeResource(mapToSource(index)); } return false; } bool importResourceFile(const QString &filename) override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->importResourceFile(filename); } return false; } bool addResource(KoResourceSP resource, const QString &storageId = QString()) override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->addResource(resource, storageId); } return false; } bool updateResource(KoResourceSP resource) override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->updateResource(resource); } return false; } + bool renameResource(KoResourceSP resource, const QString &name) override + { + KisAbstractResourceModel *source = dynamic_cast(sourceModel()); + if (source) { + return source->renameResource(resource, name); + } + return false; + } + bool removeResource(KoResourceSP resource) override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->removeResource(resource); } return false; } bool setResourceMetaData(KoResourceSP resource, QMap metadata) override { KisAbstractResourceModel *source = dynamic_cast(sourceModel()); if (source) { return source->setResourceMetaData(resource, metadata); } return false; } // QSortFilterProxyModel interface protected: QVariant data(const QModelIndex &index, int role) const override { return sourceModel()->data(mapToSource(index), role); } bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { if (m_id.isEmpty()) return true; QModelIndex idx = sourceModel()->index(source_row, 0, source_parent); QMap metadata = sourceModel()->data(idx, Qt::UserRole + KisResourceModel::MetaData).toMap(); if (metadata.contains("paintopid")) { return (metadata["paintopid"].toString() == m_id); } return false; } bool filterAcceptsColumn(int /*source_column*/, const QModelIndex &/*source_parent*/) const override { return true; } bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override { QString nameLeft = sourceModel()->data(source_left, Qt::UserRole + KisResourceModel::Name).toString(); QString nameRight = sourceModel()->data(source_right, Qt::UserRole + KisResourceModel::Name).toString(); return nameLeft < nameRight; } private: QString m_id; }; KisPresetChooser::KisPresetChooser(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QVBoxLayout * layout = new QVBoxLayout(this); layout->setMargin(0); m_paintOpFilterModel = new PaintOpFilterModel(); m_chooser = new KisResourceItemChooser(ResourceType::PaintOpPresets, false, this, m_paintOpFilterModel); m_chooser->setObjectName("ResourceChooser"); m_chooser->setRowHeight(50); m_delegate = new KisPresetDelegate(this); m_chooser->setItemDelegate(m_delegate); m_chooser->setSynced(true); layout->addWidget(m_chooser); { QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this->itemChooser()->itemView()); if (scroller) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } } connect(m_chooser, SIGNAL(resourceSelected(KoResourceSP )), this, SIGNAL(resourceSelected(KoResourceSP ))); connect(m_chooser, SIGNAL(resourceClicked(KoResourceSP )), this, SIGNAL(resourceClicked(KoResourceSP ))); m_mode = THUMBNAIL; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(notifyConfigChanged())); notifyConfigChanged(); } KisPresetChooser::~KisPresetChooser() { } void KisPresetChooser::showButtons(bool show) { m_chooser->showButtons(show); } void KisPresetChooser::setViewMode(KisPresetChooser::ViewMode mode) { m_mode = mode; updateViewSettings(); } void KisPresetChooser::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); updateViewSettings(); } void KisPresetChooser::notifyConfigChanged() { KisConfig cfg(true); m_delegate->setUseDirtyPresets(cfg.useDirtyPresets()); setIconSize(cfg.presetIconSize()); updateViewSettings(); } void KisPresetChooser::updateViewSettings() { if (m_mode == THUMBNAIL) { m_chooser->setSynced(true); m_delegate->setShowText(false); m_chooser->itemView()->setViewMode(QListView::IconMode); m_chooser->itemView()->setFlow(QListView::LeftToRight); } else if (m_mode == DETAIL) { m_chooser->setSynced(false); m_chooser->itemView()->setViewMode(QListView::ListMode); m_chooser->itemView()->setFlow(QListView::TopToBottom); m_chooser->setColumnWidth(m_chooser->width()); KisResourceItemChooserSync* chooserSync = KisResourceItemChooserSync::instance(); m_chooser->setRowHeight(chooserSync->baseLength()); m_delegate->setShowText(true); } else if (m_mode == STRIP) { m_chooser->setSynced(false); m_chooser->itemView()->setViewMode(QListView::ListMode); m_chooser->itemView()->setFlow(QListView::LeftToRight); // An offset of 7 keeps the cell exactly square, TODO: use constants, not hardcoded numbers m_chooser->setColumnWidth(m_chooser->viewSize().height() - 7); m_delegate->setShowText(false); } } void KisPresetChooser::setCurrentResource(KoResourceSP resource) { m_chooser->setCurrentResource(resource); } KoResourceSP KisPresetChooser::currentResource() const { return m_chooser->currentResource(); } void KisPresetChooser::showTaggingBar(bool show) { m_chooser->showTaggingBar(show); } KisResourceItemChooser *KisPresetChooser::itemChooser() { return m_chooser; } void KisPresetChooser::setPresetFilter(const QString& paintOpId) { if (m_paintOpFilterModel && m_paintOpFilterModel->currentPaintOpId() != paintOpId) { m_paintOpFilterModel->setPaintOpId(paintOpId); updateViewSettings(); } } void KisPresetChooser::setIconSize(int newSize) { KisResourceItemChooserSync* chooserSync = KisResourceItemChooserSync::instance(); chooserSync->setBaseLength(newSize); updateViewSettings(); } int KisPresetChooser::iconSize() { KisResourceItemChooserSync* chooserSync = KisResourceItemChooserSync::instance(); return chooserSync->baseLength(); } void KisPresetChooser::saveIconSize() { // save icon size KisConfig cfg(false); cfg.setPresetIconSize(iconSize()); } void KisPresetChooser::slotScrollerStateChanged(QScroller::State state) { KisKineticScroller::updateCursor(this, state); } #include "kis_preset_chooser.moc" diff --git a/libs/widgets/CMakeLists.txt b/libs/widgets/CMakeLists.txt index 25de1f7dfb..73b775a6b9 100644 --- a/libs/widgets/CMakeLists.txt +++ b/libs/widgets/CMakeLists.txt @@ -1,118 +1,118 @@ add_subdirectory( tests ) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(kritawidgets_LIB_SRCS KoVBox.cpp KoDialog.cpp KoZoomWidget.cpp KoAspectButton.cpp KoPagePreviewWidget.cpp KoSliderCombo.cpp KoColorPopupButton.cpp KoConfigAuthorPage.cpp KoUnitDoubleSpinBox.cpp KoZoomAction.cpp KoZoomController.cpp KoZoomInput.cpp KoZoomHandler.cpp KoZoomMode.cpp KoDpi.cpp KoColorPatch.cpp KoColorPopupAction.cpp KoColorSetWidget.cpp KoColorSlider.cpp KoTriangleColorSelector.cpp KoResourcePopupAction.cpp KoRuler.cpp KoResourceServerProvider.cpp KoLineStyleSelector.cpp KoLineStyleItemDelegate.cpp KoLineStyleModel.cpp KoTitledTabWidget.cpp KoToolBoxButton.cpp KoToolBox.cpp KoToolBoxDocker.cpp KoToolBoxFactory.cpp KoToolDocker.cpp KoPageLayoutWidget.cpp KoPageLayoutDialog.cpp KoShadowConfigWidget.cpp KoMarkerSelector.cpp KoMarkerModel.cpp KoMarkerItemDelegate.cpp KoDocumentInfoDlg.cpp WidgetsDebug.cpp kis_file_name_requester.cpp KisColorSelectorInterface.cpp KoAnchorSelectionWidget.cpp KisGradientSlider.cpp KisGradientSliderWidget.cpp kis_color_input.cpp # classes used by internal color selector kis_spinbox_color_selector.cpp KisVisualColorSelector.cpp KisVisualColorSelectorShape.cpp KisVisualEllipticalSelectorShape.cpp KisVisualRectangleSelectorShape.cpp KisVisualTriangleSelectorShape.cpp KisScreenColorPickerBase.cpp KisDlgInternalColorSelector.cpp KisPaletteModel.cpp KisPaletteDelegate.cpp kis_palette_view.cpp - KisPaletteListWidget.cpp + KisPaletteChooser.cpp KisPaletteComboBox.cpp kis_color_button.cpp ) ki18n_wrap_ui( kritawidgets_LIB_SRCS KoConfigAuthorPage.ui koDocumentInfoAboutWidget.ui koDocumentInfoAuthorWidget.ui wdg_file_name_requester.ui KoPageLayoutWidget.ui KoShadowConfigWidget.ui WdgDlgInternalColorSelector.ui WdgPaletteListWidget.ui ) add_library(kritawidgets SHARED ${kritawidgets_LIB_SRCS}) generate_export_header(kritawidgets BASE_NAME kritawidgets) target_link_libraries(kritawidgets kritaodf kritaglobal kritaflake kritapigment kritawidgetutils kritaresources kritaresourcewidgets Qt5::PrintSupport KF5::CoreAddons KF5::ConfigGui KF5::GuiAddons KF5::WidgetsAddons KF5::ConfigCore KF5::Completion ) if(X11_FOUND) target_link_libraries(kritawidgets Qt5::X11Extras ${X11_LIBRARIES}) endif() set_target_properties(kritawidgets PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritawidgets ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/widgets/KisColorsetChooser.h b/libs/widgets/KisColorsetChooser.h deleted file mode 100644 index 69d49c50d2..0000000000 --- a/libs/widgets/KisColorsetChooser.h +++ /dev/null @@ -1,55 +0,0 @@ -/* 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. - */ - - -#ifndef KIS_COLORSET_CHOOSER_H -#define KIS_COLORSET_CHOOSER_H - -#include - -class QSpinBox; -class KoColorSet; -class QLineEdit; -class KisResourceItemChooser; -class KoResource; - -#include "kritawidgets_export.h" - - -class KRITAWIDGETS_EXPORT KisColorsetChooser : public QWidget -{ - Q_OBJECT -public: - KisColorsetChooser(QWidget* parent = 0); - ~KisColorsetChooser() override; - -Q_SIGNALS: - void paletteSelected(KoColorSetSP colorSet); - -private Q_SLOTS: - void resourceSelected(KoResourceSP resource); - void slotSave(); - -private: - KisResourceItemChooser * m_itemChooser; - QLineEdit* m_nameEdit; - QSpinBox* m_columnEdit; -}; - -#endif // COLORSET_CHOOSER_H diff --git a/libs/widgets/KisDlgInternalColorSelector.cpp b/libs/widgets/KisDlgInternalColorSelector.cpp index f773267203..afb780a512 100644 --- a/libs/widgets/KisDlgInternalColorSelector.cpp +++ b/libs/widgets/KisDlgInternalColorSelector.cpp @@ -1,348 +1,348 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include #include -#include +#include #include #include #include #include "kis_signal_compressor.h" #include "KoColorDisplayRendererInterface.h" #include "kis_spinbox_color_selector.h" #include "KisDlgInternalColorSelector.h" #include "ui_WdgDlgInternalColorSelector.h" #include "kis_config_notifier.h" #include "kis_color_input.h" #include "kis_icon_utils.h" #include "KisSqueezedComboBox.h" std::function KisDlgInternalColorSelector::s_screenColorPickerFactory = 0; struct KisDlgInternalColorSelector::Private { bool allowUpdates = true; KoColor currentColor; KoColor previousColor; KoColor sRGB = KoColor(KoColorSpaceRegistry::instance()->rgb8()); const KoColorSpace *currentColorSpace; bool lockUsedCS = false; bool chooseAlpha = false; KisSignalCompressor *compressColorChanges; const KoColorDisplayRendererInterface *displayRenderer; KisHexColorInput *hexColorInput = 0; KisPaletteModel *paletteModel = 0; - KisPaletteListWidget *paletteChooser = 0; + KisPaletteChooser *paletteChooser = 0; KisScreenColorPickerBase *screenColorPicker = 0; }; KisDlgInternalColorSelector::KisDlgInternalColorSelector(QWidget *parent, KoColor color, Config config, const QString &caption, const KoColorDisplayRendererInterface *displayRenderer) : QDialog(parent) , m_d(new Private) { setModal(config.modal); setFocusPolicy(Qt::ClickFocus); m_ui = new Ui_WdgDlgInternalColorSelector(); m_ui->setupUi(this); setWindowTitle(caption); m_d->currentColor = color; m_d->currentColorSpace = m_d->currentColor.colorSpace(); m_d->displayRenderer = displayRenderer; m_ui->spinboxselector->slotSetColor(color); connect(m_ui->spinboxselector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slotColorUpdated(KoColor))); m_ui->visualSelector->slotSetColor(color); m_ui->visualSelector->setDisplayRenderer(displayRenderer); m_ui->visualSelector->setConfig(false, config.modal); if (config.visualColorSelector) { connect(m_ui->visualSelector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slotColorUpdated(KoColor))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_ui->visualSelector, SLOT(configurationChanged())); } else { m_ui->visualSelector->hide(); } - m_d->paletteChooser = new KisPaletteListWidget(this); + m_d->paletteChooser = new KisPaletteChooser(this); m_d->paletteModel = new KisPaletteModel(this); m_ui->bnPaletteChooser->setIcon(KisIconUtils::loadIcon("hi16-palette_library")); m_ui->paletteBox->setPaletteModel(m_d->paletteModel); m_ui->paletteBox->setDisplayRenderer(displayRenderer); m_ui->cmbNameList->setCompanionView(m_ui->paletteBox); connect(m_d->paletteChooser, SIGNAL(sigPaletteSelected(KoColorSetSP)), this, SLOT(slotChangePalette(KoColorSetSP))); connect(m_ui->cmbNameList, SIGNAL(sigColorSelected(KoColor)), SLOT(slotColorUpdated(KoColor))); // For some bizarre reason, the modal dialog doesn't like having the colorset set, so let's not. if (config.paletteBox) { //TODO: Add disable signal as well. Might be not necessary...? KConfigGroup cfg(KSharedConfig::openConfig()->group("")); QString paletteName = cfg.readEntry("internal_selector_active_color_set", QString()); KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); KoColorSetSP savedPal = rServer->resourceByName(paletteName); if (savedPal) { this->slotChangePalette(savedPal); } else { if (rServer->resourceCount()) { savedPal = rServer->firstResource(); if (savedPal) { this->slotChangePalette(savedPal); } } } connect(m_ui->paletteBox, SIGNAL(sigColorSelected(KoColor)), this, SLOT(slotColorUpdated(KoColor))); m_ui->bnPaletteChooser->setPopupWidget(m_d->paletteChooser); } else { m_ui->paletteBox->setEnabled(false); m_ui->cmbNameList->setEnabled(false); m_ui->bnPaletteChooser->setEnabled(false); } if (config.prevNextButtons) { m_ui->currentColor->setColor(m_d->currentColor); m_ui->currentColor->setDisplayRenderer(displayRenderer); m_ui->previousColor->setColor(m_d->currentColor); m_ui->previousColor->setDisplayRenderer(displayRenderer); connect(m_ui->previousColor, SIGNAL(triggered(KoColorPatch*)), SLOT(slotSetColorFromPatch(KoColorPatch*))); } else { m_ui->currentColor->hide(); m_ui->previousColor->hide(); } if (config.hexInput) { m_d->sRGB.fromKoColor(m_d->currentColor); m_d->hexColorInput = new KisHexColorInput(this, &m_d->sRGB); m_d->hexColorInput->update(); connect(m_d->hexColorInput, SIGNAL(updated()), SLOT(slotSetColorFromHex())); m_ui->rightPane->addWidget(m_d->hexColorInput); m_d->hexColorInput->setToolTip(i18n("This is a hexcode input, for webcolors. It can only get colors in the sRGB space.")); } // KisScreenColorPicker is in the kritaui module, so dependency inversion is used to access it. m_ui->screenColorPickerWidget->setLayout(new QHBoxLayout(m_ui->screenColorPickerWidget)); if (s_screenColorPickerFactory) { m_d->screenColorPicker = s_screenColorPickerFactory(m_ui->screenColorPickerWidget); m_ui->screenColorPickerWidget->layout()->addWidget(m_d->screenColorPicker); if (config.screenColorPicker) { connect(m_d->screenColorPicker, SIGNAL(sigNewColorPicked(KoColor)),this, SLOT(slotColorUpdated(KoColor))); } else { m_d->screenColorPicker->hide(); } } m_d->compressColorChanges = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE, this); connect(m_d->compressColorChanges, SIGNAL(timeout()), this, SLOT(endUpdateWithNewColor())); connect(m_ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()), Qt::UniqueConnection); connect(m_ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject()), Qt::UniqueConnection); connect(this, SIGNAL(finished(int)), SLOT(slotFinishUp())); } KisDlgInternalColorSelector::~KisDlgInternalColorSelector() { delete m_ui; } void KisDlgInternalColorSelector::slotColorUpdated(KoColor newColor) { // not-so-nice solution: if someone calls this slot directly and that code was // triggered by our compressor signal, our compressor is technically the sender()! if (sender() == m_d->compressColorChanges) { return; } // Do not accept external updates while a color update emit is pending; // Note: Assumes external updates only come from parent(), a separate slot might be better if (m_d->allowUpdates || (QObject::sender() && QObject::sender() != this->parent())) { // Enforce palette colors KConfigGroup group(KSharedConfig::openConfig(), ""); if (group.readEntry("colorsettings/forcepalettecolors", false)) { newColor = m_ui->paletteBox->closestColor(newColor); } if (m_d->lockUsedCS){ newColor.convertTo(m_d->currentColorSpace); m_d->currentColor = newColor; } else { m_d->currentColor = newColor; } updateAllElements(QObject::sender()); } } void KisDlgInternalColorSelector::slotSetColorFromPatch(KoColorPatch *patch) { slotColorUpdated(patch->color()); } void KisDlgInternalColorSelector::colorSpaceChanged(const KoColorSpace *cs) { if (cs == m_d->currentColorSpace) { return; } m_d->currentColorSpace = KoColorSpaceRegistry::instance()->colorSpace(cs->colorModelId().id(), cs->colorDepthId().id(), cs->profile()); m_ui->spinboxselector->slotSetColorSpace(m_d->currentColorSpace); m_ui->visualSelector->slotsetColorSpace(m_d->currentColorSpace); } void KisDlgInternalColorSelector::lockUsedColorSpace(const KoColorSpace *cs) { colorSpaceChanged(cs); if (m_d->currentColor.colorSpace() != m_d->currentColorSpace) { m_d->currentColor.convertTo(m_d->currentColorSpace); } m_d->lockUsedCS = true; } void KisDlgInternalColorSelector::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { m_d->displayRenderer = displayRenderer; m_ui->visualSelector->setDisplayRenderer(displayRenderer); m_ui->currentColor->setDisplayRenderer(displayRenderer); m_ui->previousColor->setDisplayRenderer(displayRenderer); m_ui->paletteBox->setDisplayRenderer(displayRenderer); } else { m_d->displayRenderer = KoDumbColorDisplayRenderer::instance(); } } KoColor KisDlgInternalColorSelector::getModalColorDialog(const KoColor color, QWidget* parent, QString caption) { Config config = Config(); KisDlgInternalColorSelector dialog(parent, color, config, caption); dialog.setPreviousColor(color); dialog.exec(); return dialog.getCurrentColor(); } KoColor KisDlgInternalColorSelector::getCurrentColor() { return m_d->currentColor; } void KisDlgInternalColorSelector::chooseAlpha(bool chooseAlpha) { m_d->chooseAlpha = chooseAlpha; } void KisDlgInternalColorSelector::setPreviousColor(KoColor c) { m_d->previousColor = c; } void KisDlgInternalColorSelector::reject() { slotColorUpdated(m_d->previousColor); QDialog::reject(); } void KisDlgInternalColorSelector::updateAllElements(QObject *source) { //update everything!!! if (source != m_ui->spinboxselector) { m_ui->spinboxselector->slotSetColor(m_d->currentColor); } if (source != m_ui->visualSelector) { m_ui->visualSelector->slotSetColor(m_d->currentColor); } if (source != m_d->hexColorInput) { m_d->sRGB.fromKoColor(m_d->currentColor); m_d->hexColorInput->update(); } if (source != m_ui->paletteBox) { m_ui->paletteBox->selectClosestColor(m_d->currentColor); } m_ui->previousColor->setColor(m_d->previousColor); m_ui->currentColor->setColor(m_d->currentColor); if (source && source != this->parent()) { m_d->allowUpdates = false; m_d->compressColorChanges->start(); } if (m_d->screenColorPicker) { m_d->screenColorPicker->updateIcons(); } } void KisDlgInternalColorSelector::endUpdateWithNewColor() { emit signalForegroundColorChosen(m_d->currentColor); m_d->allowUpdates = true; } void KisDlgInternalColorSelector::focusInEvent(QFocusEvent *) { //setPreviousColor(); } void KisDlgInternalColorSelector::slotFinishUp() { setPreviousColor(m_d->currentColor); KConfigGroup cfg(KSharedConfig::openConfig()->group("")); if (m_d->paletteModel) { if (m_d->paletteModel->colorSet()) { cfg.writeEntry("internal_selector_active_color_set", m_d->paletteModel->colorSet()->name()); } } } void KisDlgInternalColorSelector::slotSetColorFromHex() { slotColorUpdated(m_d->sRGB); } void KisDlgInternalColorSelector::slotChangePalette(KoColorSetSP set) { if (!set) { return; } m_d->paletteModel->setPalette(set); } void KisDlgInternalColorSelector::showEvent(QShowEvent *event) { updateAllElements(0); QDialog::showEvent(event); } diff --git a/libs/widgets/KisPaletteListWidget.cpp b/libs/widgets/KisPaletteChooser.cpp similarity index 83% rename from libs/widgets/KisPaletteListWidget.cpp rename to libs/widgets/KisPaletteChooser.cpp index ed0bd1a409..905a20744e 100644 --- a/libs/widgets/KisPaletteListWidget.cpp +++ b/libs/widgets/KisPaletteChooser.cpp @@ -1,194 +1,194 @@ /* * Copyright (c) 2013 Sven Langkamp * Copyright (c) 2018 Michael Zhou * * 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 "KisPaletteListWidget.h" -#include "KisPaletteListWidget_p.h" +#include "KisPaletteChooser.h" +#include "KisPaletteChooser_p.h" -KisPaletteListWidget::KisPaletteListWidget(QWidget *parent) +KisPaletteChooser::KisPaletteChooser(QWidget *parent) : QWidget(parent) , m_ui(new Ui_WdgPaletteListWidget) - , m_d(new KisPaletteListWidgetPrivate(this)) + , m_d(new KisPaletteChooserPrivate(this)) { m_d->allowModification = false; m_d->actAdd.reset(new QAction(KisIconUtils::loadIcon("list-add"), i18n("Add a new palette"))); m_d->actRemove.reset(new QAction(KisIconUtils::loadIcon("list-remove"), i18n("Remove current palette"))); m_d->actImport.reset(new QAction(KisIconUtils::loadIcon("document-import"), i18n("Import a new palette from file"))); m_d->actExport.reset(new QAction(KisIconUtils::loadIcon("document-export"), i18n("Export current palette to file"))); m_ui->setupUi(this); m_ui->bnAdd->setDefaultAction(m_d->actAdd.data()); m_ui->bnRemove->setDefaultAction(m_d->actRemove.data()); m_ui->bnImport->setDefaultAction(m_d->actImport.data()); m_ui->bnExport->setDefaultAction(m_d->actExport.data()); m_ui->bnAdd->setEnabled(false); m_ui->bnRemove->setEnabled(false); m_ui->bnImport->setEnabled(false); m_ui->bnExport->setEnabled(false); connect(m_d->actAdd.data(), SIGNAL(triggered()), SLOT(slotAdd())); connect(m_d->actRemove.data(), SIGNAL(triggered()), SLOT(slotRemove())); connect(m_d->actImport.data(), SIGNAL(triggered()), SLOT(slotImport())); connect(m_d->actExport.data(), SIGNAL(triggered()), SLOT(slotExport())); m_d->itemChooser->setItemDelegate(m_d->delegate.data()); m_d->itemChooser->setRowHeight(40); m_d->itemChooser->itemView()->setViewMode(QListView::ListMode); m_d->itemChooser->showButtons(false); m_d->itemChooser->showTaggingBar(true); m_ui->viewPalette->setLayout(new QHBoxLayout(m_ui->viewPalette)); m_ui->viewPalette->layout()->addWidget(m_d->itemChooser.data()); connect(m_d->itemChooser.data(), SIGNAL(resourceSelected(KoResourceSP )), SLOT(slotPaletteResourceSelected(KoResourceSP ))); } -KisPaletteListWidget::~KisPaletteListWidget() +KisPaletteChooser::~KisPaletteChooser() { } -void KisPaletteListWidget::slotPaletteResourceSelected(KoResourceSP r) +void KisPaletteChooser::slotPaletteResourceSelected(KoResourceSP r) { KoColorSetSP g = r.staticCast(); emit sigPaletteSelected(g); if (!m_d->allowModification) { return; } if (g->isEditable()) { m_ui->bnRemove->setEnabled(true); } else { m_ui->bnRemove->setEnabled(false); } } -void KisPaletteListWidget::slotAdd() +void KisPaletteChooser::slotAdd() { if (!m_d->allowModification) { return; } emit sigAddPalette(); m_d->itemChooser->setCurrentItem(m_d->itemChooser->rowCount() - 1); } -void KisPaletteListWidget::slotRemove() +void KisPaletteChooser::slotRemove() { if (!m_d->allowModification) { return; } if (m_d->itemChooser->currentResource()) { KoColorSetSP cs = m_d->itemChooser->currentResource().staticCast(); emit sigRemovePalette(cs); } m_d->itemChooser->setCurrentItem(0); } -void KisPaletteListWidget::slotImport() +void KisPaletteChooser::slotImport() { if (!m_d->allowModification) { return; } emit sigImportPalette(); m_d->itemChooser->setCurrentItem(m_d->itemChooser->rowCount() - 1); } -void KisPaletteListWidget::slotExport() +void KisPaletteChooser::slotExport() { if (!m_d->allowModification) { return; } emit sigExportPalette(m_d->itemChooser->currentResource().staticCast()); } -void KisPaletteListWidget::setAllowModification(bool allowModification) +void KisPaletteChooser::setAllowModification(bool allowModification) { m_d->allowModification = allowModification; m_ui->bnAdd->setEnabled(allowModification); m_ui->bnImport->setEnabled(allowModification); m_ui->bnExport->setEnabled(allowModification); KoColorSetSP cs = m_d->itemChooser->currentResource().staticCast(); m_ui->bnRemove->setEnabled(allowModification && cs && cs->isEditable()); } -/************************* KisPaletteListWidgetPrivate **********************/ +/************************* KisPaletteChooserPrivate **********************/ -KisPaletteListWidgetPrivate::KisPaletteListWidgetPrivate(KisPaletteListWidget *a_c) +KisPaletteChooserPrivate::KisPaletteChooserPrivate(KisPaletteChooser *a_c) : c(a_c) , itemChooser(new KisResourceItemChooser(ResourceType::Palettes, false, a_c)) , delegate(new Delegate(a_c)) { } -KisPaletteListWidgetPrivate::~KisPaletteListWidgetPrivate() +KisPaletteChooserPrivate::~KisPaletteChooserPrivate() { } -/******************* KisPaletteListWidgetPrivate::Delegate ******************/ +/******************* KisPaletteChooserPrivate::Delegate ******************/ -KisPaletteListWidgetPrivate::Delegate::Delegate(QObject *parent) +KisPaletteChooserPrivate::Delegate::Delegate(QObject *parent) : QAbstractItemDelegate(parent) { } -KisPaletteListWidgetPrivate::Delegate::~Delegate() +KisPaletteChooserPrivate::Delegate::~Delegate() { } -void KisPaletteListWidgetPrivate::Delegate::paint(QPainter *painter, +void KisPaletteChooserPrivate::Delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { painter->save(); if (!index.isValid()) return; - QImage preview = index.data(Qt::DecorationRole).value(); + QImage preview = index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); QString name = index.data(Qt::UserRole + KisResourceModel::Name).toString(); QRect previewRect(option.rect.x() + 2, option.rect.y() + 2, option.rect.height() - 4, option.rect.height() - 4); painter->drawImage(previewRect, preview); if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.highlight()); painter->drawImage(previewRect, preview); painter->setPen(option.palette.highlightedText().color()); } else { painter->setBrush(option.palette.text().color()); } painter->drawText(option.rect.x() + previewRect.width() + 10, option.rect.y() + painter->fontMetrics().ascent() + 5, name); painter->restore(); } -inline QSize KisPaletteListWidgetPrivate::Delegate::sizeHint(const QStyleOptionViewItem & option, +inline QSize KisPaletteChooserPrivate::Delegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const { return option.decorationSize; } diff --git a/libs/widgets/KisPaletteListWidget.h b/libs/widgets/KisPaletteChooser.h similarity index 87% rename from libs/widgets/KisPaletteListWidget.h rename to libs/widgets/KisPaletteChooser.h index 38c83059e1..a1a0be1795 100644 --- a/libs/widgets/KisPaletteListWidget.h +++ b/libs/widgets/KisPaletteChooser.h @@ -1,70 +1,70 @@ /* * Copyright (c) 2013 Sven Langkamp * Copyright (c) 2018 Michael Zhou * * 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 KISPALETTELISTWIDGET_H #define KISPALETTELISTWIDGET_H #include #include #include #include "kritawidgets_export.h" #include class KoResource; -struct KisPaletteListWidgetPrivate; +struct KisPaletteChooserPrivate; -class KRITAWIDGETS_EXPORT KisPaletteListWidget : public QWidget +class KRITAWIDGETS_EXPORT KisPaletteChooser : public QWidget { Q_OBJECT public: - explicit KisPaletteListWidget(QWidget *parent = nullptr); - virtual ~KisPaletteListWidget(); + explicit KisPaletteChooser(QWidget *parent = nullptr); + virtual ~KisPaletteChooser(); public: void setAllowModification(bool allowModification); Q_SIGNALS: void sigPaletteSelected(KoColorSetSP); void sigAddPalette(); void sigRemovePalette(KoColorSetSP); void sigImportPalette(); void sigExportPalette(KoColorSetSP); public Q_SLOTS: private /* methods */: QString newPaletteFileName(); private Q_SLOTS: void slotPaletteResourceSelected(KoResourceSP); void slotAdd(); void slotRemove(); void slotImport(); void slotExport(); private: QScopedPointer m_ui; - QScopedPointer m_d; + QScopedPointer m_d; }; #endif // KISPALETTELISTWIDGET_H diff --git a/libs/widgets/KisPaletteListWidget_p.h b/libs/widgets/KisPaletteChooser_p.h similarity index 87% rename from libs/widgets/KisPaletteListWidget_p.h rename to libs/widgets/KisPaletteChooser_p.h index 015fcd43d3..1cddc82201 100644 --- a/libs/widgets/KisPaletteListWidget_p.h +++ b/libs/widgets/KisPaletteChooser_p.h @@ -1,69 +1,69 @@ /* * Copyright (c) 2013 Sven Langkamp * Copyright (c) 2018 Michael Zhou * * 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 KISPALETTELISTWIDGET_P_H #define KISPALETTELISTWIDGET_P_H #include #include #include #include #include #include #include -#include +#include #include #include #include -struct KisPaletteListWidgetPrivate +struct KisPaletteChooserPrivate { class View; class Delegate; class Model; - KisPaletteListWidgetPrivate(KisPaletteListWidget *); - virtual ~KisPaletteListWidgetPrivate(); + KisPaletteChooserPrivate(KisPaletteChooser *); + virtual ~KisPaletteChooserPrivate(); bool allowModification; - QPointer c; + QPointer c; QSharedPointer itemChooser; QScopedPointer delegate; QScopedPointer actAdd; QScopedPointer actImport; QScopedPointer actExport; QScopedPointer actModify; QScopedPointer actRemove; }; -class KisPaletteListWidgetPrivate::Delegate : public QAbstractItemDelegate +class KisPaletteChooserPrivate::Delegate : public QAbstractItemDelegate { public: Delegate(QObject *); virtual ~Delegate(); void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const override; QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override; }; #endif // KISPALETTELISTWIDGET_P_H diff --git a/libs/widgets/KisPaletteModel.cpp b/libs/widgets/KisPaletteModel.cpp index 61e6cf6b5f..8df5155288 100644 --- a/libs/widgets/KisPaletteModel.cpp +++ b/libs/widgets/KisPaletteModel.cpp @@ -1,511 +1,512 @@ /* * Copyright (c) 2013 Sven Langkamp * Copyright (c) 2018 Michael Zhou * * 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 #include #include #include +#include +#include KisPaletteModel::KisPaletteModel(QObject* parent) : QAbstractTableModel(parent) , m_colorSet(0) , m_displayRenderer(KoDumbColorDisplayRenderer::instance()) { connect(this, SIGNAL(sigPaletteModified()), SLOT(slotPaletteModified())); } KisPaletteModel::~KisPaletteModel() { } QVariant KisPaletteModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } bool groupNameRow = m_rowGroupNameMap.contains(index.row()); if (role == IsGroupNameRole) { return groupNameRow; } if (groupNameRow) { return dataForGroupNameRow(index, role); } else { return dataForSwatch(index, role); } } int KisPaletteModel::rowCount(const QModelIndex& /*parent*/) const { if (!m_colorSet) return 0; return m_colorSet->rowCount() // count of color rows + m_rowGroupNameMap.size() // rows for names - 1; // global doesn't have a name } int KisPaletteModel::columnCount(const QModelIndex& /*parent*/) const { if (m_colorSet && m_colorSet->columnCount() > 0) { return m_colorSet->columnCount(); } if (!m_colorSet) { return 0; } return 16; } Qt::ItemFlags KisPaletteModel::flags(const QModelIndex& index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } return Qt::ItemIsDropEnabled; } QModelIndex KisPaletteModel::index(int row, int column, const QModelIndex& parent) const { Q_UNUSED(parent) Q_ASSERT(m_colorSet); if (m_rowGroupNameMap.isEmpty()) { return {}; } int groupNameRow = groupNameRowForRow(row); KisSwatchGroup *group = m_colorSet->getGroup(m_rowGroupNameMap[groupNameRow]); KIS_ASSERT_RECOVER_RETURN_VALUE(group,QModelIndex()); return createIndex(row, column, group); } void KisPaletteModel::resetGroupNameRows() { m_rowGroupNameMap.clear(); int row = -1; for (const QString &groupName : m_colorSet->getGroupNames()) { m_rowGroupNameMap[row] = groupName; row += m_colorSet->getGroup(groupName)->rowCount(); row += 1; // row for group name } } void KisPaletteModel::setPalette(KoColorSetSP palette) { beginResetModel(); m_colorSet = palette; if (palette) { resetGroupNameRows(); } endResetModel(); emit sigPaletteChanged(); } KoColorSetSP KisPaletteModel::colorSet() const { return m_colorSet; } int KisPaletteModel::rowNumberInGroup(int rowInModel) const { if (m_rowGroupNameMap.contains(rowInModel)) { return -1; } QList rowNumberList = m_rowGroupNameMap.keys(); for (auto it = rowNumberList.rbegin(); it != rowNumberList.rend(); it++) { if (*it < rowInModel) { return rowInModel - *it - 1; } } return rowInModel; } int KisPaletteModel::groupNameRowForName(const QString &groupName) { for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) { if (it.value() == groupName) { return it.key(); } } return -1; } bool KisPaletteModel::addEntry(const KisSwatch &entry, const QString &groupName) { beginInsertRows(QModelIndex(), rowCount(), rowCount() + 1); m_colorSet->add(entry, groupName); endInsertRows(); - if (m_colorSet->isGlobal()) { - m_colorSet->save(); - } + saveModification(); emit sigPaletteModified(); return true; } bool KisPaletteModel::removeEntry(const QModelIndex &index, bool keepColors) { if (!qvariant_cast(data(index, IsGroupNameRole))) { static_cast(index.internalPointer())->removeEntry(index.column(), rowNumberInGroup(index.row())); emit dataChanged(index, index); } else { int groupNameRow = groupNameRowForRow(index.row()); QString groupName = m_rowGroupNameMap[groupNameRow]; removeGroup(groupName, keepColors); } emit sigPaletteModified(); return true; } void KisPaletteModel::removeGroup(const QString &groupName, bool keepColors) { int removeStart = groupNameRowForName(groupName); int removedRowCount = m_colorSet->getGroup(groupName)->rowCount(); int insertStart = m_colorSet->getGlobalGroup()->rowCount(); beginRemoveRows(QModelIndex(), removeStart, removeStart + removedRowCount); m_colorSet->removeGroup(groupName, keepColors); resetGroupNameRows(); endRemoveRows(); beginInsertRows(QModelIndex(), - insertStart, m_colorSet->getGlobalGroup()->rowCount()); + insertStart, m_colorSet->getGlobalGroup()->rowCount()); endInsertRows(); emit sigPaletteModified(); } bool KisPaletteModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); if (!data->hasFormat("krita/x-colorsetentry") && !data->hasFormat("krita/x-colorsetgroup")) { return false; } if (action == Qt::IgnoreAction) { return false; } QModelIndex finalIndex = parent; if (!finalIndex.isValid()) { return false; } if (data->hasFormat("krita/x-colorsetgroup")) { // dragging group not supported for now QByteArray encodedData = data->data("krita/x-colorsetgroup"); QDataStream stream(&encodedData, QIODevice::ReadOnly); while (!stream.atEnd()) { QString groupNameDroppedOn = qvariant_cast(finalIndex.data(GroupNameRole)); if (groupNameDroppedOn == KoColorSet::GLOBAL_GROUP_NAME) { return false; } QString groupNameDragged; stream >> groupNameDragged; KisSwatchGroup *groupDragged = m_colorSet->getGroup(groupNameDragged); int start = groupNameRowForName(groupNameDragged); int end = start + groupDragged->rowCount(); if (!beginMoveRows(QModelIndex(), start, end, QModelIndex(), groupNameRowForName(groupNameDroppedOn))) { return false; } m_colorSet->moveGroup(groupNameDragged, groupNameDroppedOn); resetGroupNameRows(); endMoveRows(); emit sigPaletteModified(); - if (m_colorSet->isGlobal()) { - m_colorSet->save(); - } + saveModification(); } return true; } if (qvariant_cast(finalIndex.data(KisPaletteModel::IsGroupNameRole))) { return true; } QByteArray encodedData = data->data("krita/x-colorsetentry"); QDataStream stream(&encodedData, QIODevice::ReadOnly); while (!stream.atEnd()) { KisSwatch entry; QString name, id; bool spotColor; QString oldGroupName; int oriRow; int oriColumn; QString colorXml; stream >> name >> id >> spotColor >> oriRow >> oriColumn >> oldGroupName >> colorXml; entry.setName(name); entry.setId(id); entry.setSpotColor(spotColor); QDomDocument doc; doc.setContent(colorXml); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement(); if (!c.isNull()) { QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id()); entry.setColor(KoColor::fromXML(c, colorDepthId)); } if (action == Qt::MoveAction){ KisSwatchGroup *g = m_colorSet->getGroup(oldGroupName); if (g) { if (qvariant_cast(finalIndex.data(KisPaletteModel::CheckSlotRole))) { g->setEntry(getEntry(finalIndex), oriColumn, oriRow); } else { g->removeEntry(oriColumn, oriRow); } } setEntry(entry, finalIndex); emit sigPaletteModified(); - if (m_colorSet->isGlobal()) { - m_colorSet->save(); - } + saveModification(); } } return true; } QMimeData *KisPaletteModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); QModelIndex index = indexes.last(); if (index.isValid() && qvariant_cast(index.data(CheckSlotRole))) { QString mimeTypeName = "krita/x-colorsetentry"; if (qvariant_cast(index.data(IsGroupNameRole))==false) { KisSwatch entry = getEntry(index); QDomDocument doc; QDomElement root = doc.createElement("Color"); root.setAttribute("bitdepth", entry.color().colorSpace()->colorDepthId().id()); doc.appendChild(root); entry.color().toXML(doc, root); stream << entry.name() << entry.id() << entry.spotColor() << rowNumberInGroup(index.row()) << index.column() << qvariant_cast(index.data(GroupNameRole)) << doc.toString(); } else { mimeTypeName = "krita/x-colorsetgroup"; QString groupName = qvariant_cast(index.data(GroupNameRole)); stream << groupName; } mimeData->setData(mimeTypeName, encodedData); } return mimeData; } QStringList KisPaletteModel::mimeTypes() const { return QStringList() << "krita/x-colorsetentry" << "krita/x-colorsetgroup"; } Qt::DropActions KisPaletteModel::supportedDropActions() const { return Qt::MoveAction; } void KisPaletteModel::setEntry(const KisSwatch &entry, const QModelIndex &index) { KisSwatchGroup *group = static_cast(index.internalPointer()); Q_ASSERT(group); group->setEntry(entry, index.column(), rowNumberInGroup(index.row())); emit sigPaletteModified(); emit dataChanged(index, index); - if (m_colorSet->isGlobal()) { - m_colorSet->save(); - } + saveModification(); } bool KisPaletteModel::renameGroup(const QString &groupName, const QString &newName) { beginResetModel(); bool success = m_colorSet->changeGroupName(groupName, newName); for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) { if (it.value() == groupName) { m_rowGroupNameMap[it.key()] = newName; break; } } endResetModel(); emit sigPaletteModified(); return success; } void KisPaletteModel::addGroup(const KisSwatchGroup &group) { beginInsertRows(QModelIndex(), rowCount(), rowCount() + group.rowCount()); m_colorSet->addGroup(group.name()); *m_colorSet->getGroup(group.name()) = group; endInsertColumns(); emit sigPaletteModified(); } void KisPaletteModel::setRowNumber(const QString &groupName, int rowCount) { beginResetModel(); KisSwatchGroup *g = m_colorSet->getGroup(groupName); if (g) { g->setRowCount(rowCount); } endResetModel(); } void KisPaletteModel::clear() { beginResetModel(); m_colorSet->clear(); endResetModel(); } QVariant KisPaletteModel::dataForGroupNameRow(const QModelIndex &idx, int role) const { KisSwatchGroup *group = static_cast(idx.internalPointer()); Q_ASSERT(group); QString groupName = group->name(); switch (role) { case Qt::ToolTipRole: case Qt::DisplayRole: { return groupName; } case GroupNameRole: { return groupName; } case CheckSlotRole: { return true; } case RowInGroupRole: { return -1; } default: { return QVariant(); } } } QVariant KisPaletteModel::dataForSwatch(const QModelIndex &idx, int role) const { KisSwatchGroup *group = static_cast(idx.internalPointer()); Q_ASSERT(group); int rowInGroup = rowNumberInGroup(idx.row()); bool entryPresent = group->checkEntry(idx.column(), rowInGroup); KisSwatch entry; if (entryPresent) { entry = group->getEntry(idx.column(), rowInGroup); } switch (role) { case Qt::ToolTipRole: case Qt::DisplayRole: { return entryPresent ? entry.name() : i18n("Empty slot"); } case Qt::BackgroundRole: { QColor color(0, 0, 0, 0); if (entryPresent) { color = m_displayRenderer->toQColor(entry.color()); } return QBrush(color); } case GroupNameRole: { return group->name(); } case CheckSlotRole: { return entryPresent; } case RowInGroupRole: { return rowInGroup; } default: { return QVariant(); } } } void KisPaletteModel::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { if (m_displayRenderer) { disconnect(m_displayRenderer, 0, this, 0); } m_displayRenderer = displayRenderer; connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(slotDisplayConfigurationChanged()), Qt::UniqueConnection); } else { m_displayRenderer = KoDumbColorDisplayRenderer::instance(); } } +void KisPaletteModel::saveModification() +{ + qDebug() << "saving modification in palette model" << m_colorSet->filename() << m_colorSet->storageLocation(); + KisResourceModel *model = KisResourceModelProvider::resourceModel(m_colorSet->resourceType().first); + model->updateResource(m_colorSet); +} + void KisPaletteModel::slotDisplayConfigurationChanged() { beginResetModel(); endResetModel(); } void KisPaletteModel::slotPaletteModified() { m_colorSet->setPaletteType(KoColorSet::KPL); } QModelIndex KisPaletteModel::indexForClosest(const KoColor &compare) { KisSwatchGroup::SwatchInfo info = colorSet()->getClosestColorInfo(compare); return createIndex(indexRowForInfo(info), info.column, colorSet()->getGroup(info.group)); } int KisPaletteModel::indexRowForInfo(const KisSwatchGroup::SwatchInfo &info) { for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) { if (it.value() == info.group) { return it.key() + info.row + 1; } } return info.row; } KisSwatch KisPaletteModel::getEntry(const QModelIndex &index) const { KisSwatchGroup *group = static_cast(index.internalPointer()); if (!group || !group->checkEntry(index.column(), rowNumberInGroup(index.row()))) { return KisSwatch(); } return group->getEntry(index.column(), rowNumberInGroup(index.row())); } int KisPaletteModel::groupNameRowForRow(int rowInModel) const { return rowInModel - rowNumberInGroup(rowInModel) - 1; } diff --git a/libs/widgets/KisPaletteModel.h b/libs/widgets/KisPaletteModel.h index 13148b35b0..3509753502 100644 --- a/libs/widgets/KisPaletteModel.h +++ b/libs/widgets/KisPaletteModel.h @@ -1,180 +1,182 @@ /* * Copyright (c) 2013 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PALETTEMODEL_H #define KIS_PALETTEMODEL_H #include #include #include #include #include "kritawidgets_export.h" #include #include class KoColorSet; class KisPaletteView; /** * @brief The KisPaletteModel class * This, together with KisPaletteView and KisPaletteDelegate forms a mvc way to access kocolorsets. * A display renderer is given to this model to convert KoColor to QColor when * colors are requested */ class KRITAWIDGETS_EXPORT KisPaletteModel : public QAbstractTableModel { Q_OBJECT public: explicit KisPaletteModel(QObject* parent = 0); ~KisPaletteModel() override; enum AdditionalRoles { IsGroupNameRole = Qt::UserRole + 1, CheckSlotRole, GroupNameRole, RowInGroupRole }; public /* overridden methods */: // QAbstractTableModel QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; /** * @brief index * @param row * @param column * @param parent * @return the index of for the data at row, column * if the data is a color entry, the internal pointer points to the group * the entry belongs to, and the row and column are row number and column * number inside the group. * if the data is a group, the row number and group number is Q_INFINIFY, * and the internal pointer also points to the group */ QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; Qt::ItemFlags flags(const QModelIndex& index) const override; /** * @brief dropMimeData * This is an overridden function that handles dropped mimedata. * right now only colorsetentries and colorsetgroups are handled. * @return */ bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; /** * @brief mimeData * gives the mimedata for a kocolorsetentry or a kocolorsetgroup. * @param indexes * @return the mimedata for the given indices */ QMimeData *mimeData(const QModelIndexList &indexes) const override; QStringList mimeTypes() const override; Qt::DropActions supportedDropActions() const override; /** * @brief setData * setData is not used as KoColor is not a QVariant * use setEntry, addEntry and removeEntry instead */ // TODO Used QVariant::setValue and QVariant.value to implement this // bool setData(const QModelIndex &index, const QVariant &value, int role) override; Q_SIGNALS: /** * @brief sigPaletteModified * emitted when palette associated with the model is modified */ void sigPaletteModified(); /** * @brief sigPaletteChanged * emitted when the palette associated with the model is made another one */ void sigPaletteChanged(); public /* methods */: /** * @brief addEntry * proper function to handle adding entries. * @return whether successful. */ bool addEntry(const KisSwatch &entry, const QString &groupName = KoColorSet::GLOBAL_GROUP_NAME); void setEntry(const KisSwatch &entry, const QModelIndex &index); /** * @brief removeEntry * proper function to remove the colorsetentry at the given index. * The consolidates both removeentry and removegroup. * @param index the given index * @param keepColors This bool determines whether, when deleting a group, * the colors should be added to the default group. This is usually desirable, * so hence the default is true. * @return if successful */ bool removeEntry(const QModelIndex &index, bool keepColors=true); void removeGroup(const QString &groupName, bool keepColors); bool renameGroup(const QString &groupName, const QString &newName); void addGroup(const KisSwatchGroup &group); void setRowNumber(const QString &groupName, int rowCount); void clear(); KisSwatch getEntry(const QModelIndex &index) const; void setPalette(KoColorSetSP colorSet); KoColorSetSP colorSet() const; QModelIndex indexForClosest(const KoColor &compare); int indexRowForInfo(const KisSwatchGroup::SwatchInfo &info); public Q_SLOTS: private Q_SLOTS: void slotDisplayConfigurationChanged(); void slotPaletteModified(); private /* methods */: QVariant dataForGroupNameRow(const QModelIndex &idx, int role) const; QVariant dataForSwatch(const QModelIndex &idx, int role) const; int rowNumberInGroup(int rowInModel) const; int groupNameRowForRow(int rowInModel) const; int groupNameRowForName(const QString &groupName); void resetGroupNameRows(); /** * Installs a display renderer object for a palette that will * convert the KoColor to the displayable QColor. Default is the * dumb renderer. */ void setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer); + void saveModification(); + private /* member variables */: QSharedPointer m_colorSet; QPointer m_displayRenderer; QMap m_rowGroupNameMap; friend class KisPaletteView; }; #endif diff --git a/libs/widgets/KoColorSetWidget.cpp b/libs/widgets/KoColorSetWidget.cpp index feab396c8e..e1191e599e 100644 --- a/libs/widgets/KoColorSetWidget.cpp +++ b/libs/widgets/KoColorSetWidget.cpp @@ -1,212 +1,212 @@ /* 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 #include 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(slotPatchTriggered(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->numRecents = 0; d->recentsLayout = new QHBoxLayout; 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->paletteView = new KisPaletteView(this); KisPaletteModel *paletteModel = new KisPaletteModel(d->paletteView); d->paletteView->setPaletteModel(paletteModel); d->paletteView->setDisplayRenderer(d->displayRenderer); - d->paletteChooser = new KisPaletteListWidget(this); + d->paletteChooser = new KisPaletteChooser(this); d->paletteChooserButton = new KisPopupButton(this); d->paletteChooserButton->setPopupWidget(d->paletteChooser); d->paletteChooserButton->setIcon(KisIconUtils::loadIcon("hi16-palette_library")); d->paletteChooserButton->setToolTip(i18n("Choose palette")); d->colorNameCmb = new KisPaletteComboBox(this); d->colorNameCmb->setCompanionView(d->paletteView); d->bottomLayout = new QHBoxLayout; d->bottomLayout->addWidget(d->paletteChooserButton); d->bottomLayout->addWidget(d->colorNameCmb); d->bottomLayout->setStretch(0, 0); // minimize chooser button d->bottomLayout->setStretch(1, 1); // maximize color name cmb d->mainLayout = new QVBoxLayout(this); d->mainLayout->setMargin(4); d->mainLayout->setSpacing(2); d->mainLayout->addLayout(d->recentsLayout); d->mainLayout->addWidget(d->paletteView); d->mainLayout->addLayout(d->bottomLayout); setLayout(d->mainLayout); connect(d->paletteChooser, SIGNAL(sigPaletteSelected(KoColorSetSP)), SLOT(slotPaletteChoosen(KoColorSetSP))); connect(d->paletteView, SIGNAL(sigColorSelected(KoColor)), SLOT(slotColorSelectedByPalette(KoColor))); connect(d->colorNameCmb, SIGNAL(sigColorSelected(KoColor)), SLOT(slotNameListSelection(KoColor))); d->rServer = KoResourceServerProvider::instance()->paletteServer(); KoColorSetSP defaultColorSet = d->rServer->resourceByName("Default"); if (!defaultColorSet && d->rServer->resourceCount() > 0) { defaultColorSet = d->rServer->firstResource(); } setColorSet(defaultColorSet); } KoColorSetWidget::~KoColorSetWidget() { delete d; } void KoColorSetWidget::setColorSet(KoColorSetSP colorSet) { if (!colorSet) return; if (colorSet == d->colorSet) return; d->paletteView->paletteModel()->setPalette(colorSet); d->colorSet = colorSet; } KoColorSetSP KoColorSetWidget::colorSet() { return d->colorSet; } void KoColorSetWidget::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { d->displayRenderer = 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); } void KoColorSetWidget::slotColorSelectedByPalette(const KoColor &color) { emit colorChanged(color, true); d->addRecent(color); } void KoColorSetWidget::slotPatchTriggered(KoColorPatch *patch) { emit colorChanged(patch->color(), true); int i; for (i = 0; i < d->numRecents; i++) { if(patch == d->recentPatches[i]) { d->activateRecent(i); break; } } if (i == d->numRecents) { // we didn't find it above d->addRecent(patch->color()); } } void KoColorSetWidget::slotPaletteChoosen(KoColorSetSP colorSet) { d->colorSet = colorSet; d->paletteView->paletteModel()->setPalette(colorSet); } void KoColorSetWidget::slotNameListSelection(const KoColor &color) { emit colorChanged(color, true); } //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 704e8cc9a1..aaf99faf16 100644 --- a/libs/widgets/KoColorSetWidget_p.h +++ b/libs/widgets/KoColorSetWidget_p.h @@ -1,80 +1,80 @@ /* 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 +#include #include #include #include class KoColorPatch; class KisPaletteView; class Q_DECL_HIDDEN KoColorSetWidget::KoColorSetWidgetPrivate { public: KoColorSetWidget *thePublic; KoColorSetSP colorSet; KisPaletteView *paletteView; - KisPaletteListWidget *paletteChooser; + KisPaletteChooser *paletteChooser; KisPopupButton *paletteChooserButton; QVBoxLayout *mainLayout; QVBoxLayout *colorSetLayout; QHBoxLayout *recentsLayout; QHBoxLayout *bottomLayout; KoColorPatch *recentPatches[6]; QToolButton *addRemoveButton; KisPaletteComboBox *colorNameCmb; int numRecents; const KoColorDisplayRendererInterface *displayRenderer; KoResourceServer *rServer; void addRecent(const KoColor &); void activateRecent(int i); void addRemoveColors(); }; #endif diff --git a/libs/widgets/kis_palette_view.cpp b/libs/widgets/kis_palette_view.cpp index df74a71b46..e98b638ef2 100644 --- a/libs/widgets/kis_palette_view.cpp +++ b/libs/widgets/kis_palette_view.cpp @@ -1,300 +1,310 @@ /* * 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_palette_view.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisPaletteDelegate.h" #include "KisPaletteModel.h" #include "kis_color_button.h" #include +#include +#include int KisPaletteView::MININUM_ROW_HEIGHT = 10; struct KisPaletteView::Private { QPointer model; bool allowPaletteModification {false}; // if modification is allowed from this widget }; KisPaletteView::KisPaletteView(QWidget *parent) : QTableView(parent) , m_d(new Private) { setItemDelegate(new KisPaletteDelegate(this)); setShowGrid(true); setDropIndicatorShown(true); setDragDropMode(QAbstractItemView::InternalMove); setSelectionMode(QAbstractItemView::SingleSelection); setDragEnabled(false); setAcceptDrops(false); /* * without this, a cycle might be created: * the view stretches to right border, and this make it need a scroll bar; * after the bar is added, the view shrinks to the bar, and this makes it * no longer need the bar any more, and the bar is removed again */ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); // set the size of swatches horizontalHeader()->setVisible(false); verticalHeader()->setVisible(false); horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); horizontalHeader()->setMinimumSectionSize(MININUM_ROW_HEIGHT); verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); verticalHeader()->setMinimumSectionSize(MININUM_ROW_HEIGHT); connect(horizontalHeader(), SIGNAL(sectionResized(int,int,int)), SLOT(slotHorizontalHeaderResized(int,int,int))); setAutoFillBackground(true); QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this); if (scroller) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } connect(this, SIGNAL(clicked(QModelIndex)), SLOT(slotCurrentSelectionChanged(QModelIndex))); } KisPaletteView::~KisPaletteView() { } void KisPaletteView::setCrossedKeyword(const QString &value) { KisPaletteDelegate *delegate = dynamic_cast(itemDelegate()); KIS_ASSERT_RECOVER_RETURN(delegate); delegate->setCrossedKeyword(value); } bool KisPaletteView::addEntryWithDialog(KoColor color) { QScopedPointer window(new KoDialog(this)); window->setWindowTitle(i18nc("@title:window", "Add a new Colorset Entry")); QFormLayout *editableItems = new QFormLayout(window.data()); window->mainWidget()->setLayout(editableItems); QComboBox *cmbGroups = new QComboBox(window.data()); QString defaultGroupName = i18nc("Name for default group", "Default"); cmbGroups->addItem(defaultGroupName); cmbGroups->addItems(m_d->model->colorSet()->getGroupNames()); QLineEdit *lnIDName = new QLineEdit(window.data()); QLineEdit *lnName = new QLineEdit(window.data()); KisColorButton *bnColor = new KisColorButton(window.data()); QCheckBox *chkSpot = new QCheckBox(window.data()); chkSpot->setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color.")); editableItems->addRow(i18n("Group"), cmbGroups); editableItems->addRow(i18n("ID"), lnIDName); editableItems->addRow(i18n("Name"), lnName); editableItems->addRow(i18n("Color"), bnColor); editableItems->addRow(i18nc("Spot color", "Spot"), chkSpot); cmbGroups->setCurrentIndex(0); lnName->setText(i18nc("Part of a default name for a color","Color")+" " + QString::number(m_d->model->colorSet()->colorCount()+1)); lnIDName->setText(QString::number(m_d->model->colorSet()->colorCount() + 1)); bnColor->setColor(color); chkSpot->setChecked(false); if (window->exec() == KoDialog::Accepted) { QString groupName = cmbGroups->currentText(); if (groupName == defaultGroupName) { groupName = QString(); } KisSwatch newEntry; newEntry.setColor(bnColor->color()); newEntry.setName(lnName->text()); newEntry.setId(lnIDName->text()); newEntry.setSpotColor(chkSpot->isChecked()); m_d->model->addEntry(newEntry, groupName); + saveModification(); return true; } return false; } bool KisPaletteView::addGroupWithDialog() { KoDialog *window = new KoDialog(); window->setWindowTitle(i18nc("@title:window","Add a new group")); QFormLayout *editableItems = new QFormLayout(); window->mainWidget()->setLayout(editableItems); QLineEdit *lnName = new QLineEdit(); editableItems->addRow(i18nc("Name for a group", "Name"), lnName); lnName->setText(i18nc("Part of default name for a new group", "Color Group")+""+QString::number(m_d->model->colorSet()->getGroupNames().size()+1)); if (window->exec() == KoDialog::Accepted) { KisSwatchGroup group; group.setName(lnName->text()); m_d->model->addGroup(group); - m_d->model->colorSet()->save(); + saveModification(); return true; } return false; } bool KisPaletteView::removeEntryWithDialog(QModelIndex index) { bool keepColors = false; if (qvariant_cast(index.data(KisPaletteModel::IsGroupNameRole))) { QScopedPointer window(new KoDialog(this)); window->setWindowTitle(i18nc("@title:window","Removing Group")); QFormLayout *editableItems = new QFormLayout(window.data()); QCheckBox *chkKeep = new QCheckBox(window.data()); window->mainWidget()->setLayout(editableItems); editableItems->addRow(i18nc("Shows up when deleting a swatch group", "Keep the Colors"), chkKeep); if (window->exec() != KoDialog::Accepted) { return false; } keepColors = chkKeep->isChecked(); } m_d->model->removeEntry(index, keepColors); - if (m_d->model->colorSet()->isGlobal()) { - m_d->model->colorSet()->save(); - } + + saveModification(); + return true; } void KisPaletteView::selectClosestColor(const KoColor &color) { KoColorSetSP color_set = m_d->model->colorSet(); if (!color_set) { return; } //also don't select if the color is the same as the current selection if (m_d->model->getEntry(currentIndex()).color() == color) { return; } selectionModel()->clearSelection(); QModelIndex index = m_d->model->indexForClosest(color); selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select); } const KoColor KisPaletteView::closestColor(const KoColor &color) const { QModelIndex index = m_d->model->indexForClosest(color); KisSwatch swatch = m_d->model->getEntry(index); return swatch.color(); } void KisPaletteView::slotFGColorChanged(const KoColor &color) { selectClosestColor(color); } void KisPaletteView::setPaletteModel(KisPaletteModel *model) { if (m_d->model) { disconnect(m_d->model, 0, this, 0); } m_d->model = model; setModel(model); slotAdditionalGuiUpdate(); connect(model, SIGNAL(sigPaletteModified()), SLOT(slotAdditionalGuiUpdate())); connect(model, SIGNAL(sigPaletteChanged()), SLOT(slotAdditionalGuiUpdate())); } KisPaletteModel* KisPaletteView::paletteModel() const { return m_d->model; } void KisPaletteView::setAllowModification(bool allow) { m_d->allowPaletteModification = allow; setDragEnabled(allow); setAcceptDrops(allow); } void KisPaletteView::slotHorizontalHeaderResized(int, int, int newSize) { resizeRows(newSize); slotAdditionalGuiUpdate(); } void KisPaletteView::resizeRows(int newSize) { verticalHeader()->setDefaultSectionSize(newSize); verticalHeader()->resizeSections(QHeaderView::Fixed); } +void KisPaletteView::saveModification() +{ + qDebug() << "saving modification in palette view" << m_d->model->colorSet()->filename() << m_d->model->colorSet()->storageLocation(); + KisResourceModel *model = KisResourceModelProvider::resourceModel(m_d->model->colorSet()->resourceType().first); + model->updateResource(m_d->model->colorSet()); +} + void KisPaletteView::removeSelectedEntry() { if (selectedIndexes().size() <= 0) { return; } m_d->model->removeEntry(currentIndex()); } void KisPaletteView::slotAdditionalGuiUpdate() { clearSpans(); resizeRows(verticalHeader()->defaultSectionSize()); for (int groupNameRowNumber : m_d->model->m_rowGroupNameMap.keys()) { if (groupNameRowNumber == -1) { continue; } setSpan(groupNameRowNumber, 0, 1, m_d->model->columnCount()); setRowHeight(groupNameRowNumber, fontMetrics().lineSpacing() + 6); verticalHeader()->resizeSection(groupNameRowNumber, fontMetrics().lineSpacing() + 6); } } void KisPaletteView::slotCurrentSelectionChanged(const QModelIndex &newCurrent) { if (!newCurrent.isValid()) { return; } const bool isGroupName = newCurrent.data(KisPaletteModel::IsGroupNameRole).toBool(); const bool isCheckSlot = newCurrent.data(KisPaletteModel::CheckSlotRole).toBool(); const KisSwatch newEntry = m_d->model->getEntry(newCurrent); emit sigIndexSelected(newCurrent); if (isGroupName) { return; } if (isCheckSlot) { emit sigColorSelected(newEntry.color()); } } void KisPaletteView::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) { Q_ASSERT(m_d->model); m_d->model->setDisplayRenderer(displayRenderer); } diff --git a/libs/widgets/kis_palette_view.h b/libs/widgets/kis_palette_view.h index 65bae3620f..d8eff6e116 100644 --- a/libs/widgets/kis_palette_view.h +++ b/libs/widgets/kis_palette_view.h @@ -1,130 +1,131 @@ /* * Copyright (c) 2016 Dmitry Kazakov * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU 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_PALETTE_VIEW_H #define __KIS_PALETTE_VIEW_H #include #include #include #include #include #include #include #include "kritawidgets_export.h" #include class KisPaletteModel; class QWheelEvent; class KoColorDisplayRendererInterface; class KRITAWIDGETS_EXPORT KisPaletteView : public QTableView { Q_OBJECT private: static int MININUM_ROW_HEIGHT; public: explicit KisPaletteView(QWidget *parent = 0); ~KisPaletteView() override; void setPaletteModel(KisPaletteModel *model); KisPaletteModel* paletteModel() const; public: /** * @brief setAllowModification * Set whether doubleclick calls up a modification window. This is to prevent users from editing * the palette when the palette is intended to be a list of items. */ void setAllowModification(bool allow); void setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer); /** * @brief setCrossedKeyword * this apparently allows you to set keywords that can cross out colors. * This is implemented to mark the lazybrush "transparent" color. * @param value */ void setCrossedKeyword(const QString &value); void removeSelectedEntry(); /** * @brief selectClosestColor * select a color that's closest to parameter color * @param color */ void selectClosestColor(const KoColor &color); /** * @brief closestColor * determines closest swatch in the active palette and returns it's color as KoColor * @param color * @return KoColor */ const KoColor closestColor(const KoColor& color) const; /** * add an entry with a dialog window. * @warning deprecated. * kept for compatibility with PaletteView in libkis */ bool addEntryWithDialog(KoColor color); /** * remove entry with a dialog window.(Necessary for groups. * @warning deprecated. * kept for compatibility with PaletteView in libkis */ bool removeEntryWithDialog(QModelIndex index); /** * add entry with a dialog window. * @warning deprecated. * kept for compatibility with PaletteView in libkis */ bool addGroupWithDialog(); Q_SIGNALS: void sigIndexSelected(const QModelIndex &index); void sigColorSelected(const KoColor &); public Q_SLOTS: /** * This tries to select the closest color in the palette. * This doesn't update the foreground color, just the visual selection. */ void slotFGColorChanged(const KoColor &); void slotScrollerStateChanged(QScroller::State state){KisKineticScroller::updateCursor(this, state);} private Q_SLOTS: void slotHorizontalHeaderResized(int, int, int newSize); void slotAdditionalGuiUpdate(); void slotCurrentSelectionChanged(const QModelIndex &newCurrent); private: void resizeRows(int newSize); + void saveModification(); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_PALETTE_VIEW_H */ diff --git a/plugins/dockers/gamutmask/KisGamutMaskChooser.cpp b/plugins/dockers/gamutmask/KisGamutMaskChooser.cpp index fda9999ce9..088a6be379 100644 --- a/plugins/dockers/gamutmask/KisGamutMaskChooser.cpp +++ b/plugins/dockers/gamutmask/KisGamutMaskChooser.cpp @@ -1,243 +1,243 @@ /* * Copyright (c) 2018 Anna Medonosova * * 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 "KisGamutMaskChooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /// The resource item delegate for rendering the resource preview class KisGamutMaskDelegate: public QAbstractItemDelegate { public: KisGamutMaskDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) , m_mode(KisGamutMaskChooser::ViewMode::THUMBNAIL) {} ~KisGamutMaskDelegate() 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 setViewMode(KisGamutMaskChooser::ViewMode mode) { m_mode = mode; } private: KisGamutMaskChooser::ViewMode m_mode; }; void KisGamutMaskDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { painter->save(); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); if (!index.isValid()) return; - QImage preview = index.data(Qt::UserRole + KisResourceModel::Image).value(); + QImage preview = index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); QString name = index.data(Qt::UserRole + KisResourceModel::Name).value(); if(preview.isNull()) { return; } QRect paintRect = option.rect.adjusted(1, 1, -1, -1); if (m_mode == KisGamutMaskChooser::ViewMode::THUMBNAIL) { painter->drawImage(paintRect.x(), paintRect.y(), preview.scaled(paintRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); if (option.state & QStyle::State_Selected) { painter->setCompositionMode(QPainter::CompositionMode_Overlay); painter->setOpacity(0.5); painter->fillRect(paintRect, Qt::white); painter->setCompositionMode(QPainter::CompositionMode_SourceOver); painter->setOpacity(1.0); painter->setPen(QPen(option.palette.highlight(), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); QRect selectedBorder = option.rect.adjusted(1, 1, -1, -1); painter->drawRect(selectedBorder); } } else { QSize previewSize(paintRect.height(), paintRect.height()); painter->drawImage(paintRect.x(), paintRect.y(), preview.scaled(previewSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); int leftMargin = 8; int rightMargin = 7; int vertMargin = 4; int descOffset = 7; QFont font = option.font; font.setBold(true); painter->setFont(font); QRectF titleRect(QPointF(previewSize.width() + leftMargin, paintRect.y() + vertMargin), QPointF(paintRect.width() - rightMargin, paintRect.y() + descOffset + painter->fontMetrics().lineSpacing())); painter->drawText(titleRect, Qt::AlignLeft, painter->fontMetrics().elidedText( name, Qt::ElideRight, titleRect.width() ) ); /* * We currently cannot actually get the mask description, so lets stop this for now. if (!mask->description().isEmpty() && !mask->description().isNull()) { font.setPointSize(font.pointSize()-1); font.setBold(false); font.setStyle(QFont::StyleItalic); painter->setFont(font); QRectF descRect(QPointF(previewSize.width() + leftMargin, paintRect.y() + descOffset + painter->fontMetrics().lineSpacing()), QPointF(paintRect.right() - rightMargin, paintRect.bottom() - vertMargin)); int numLines = floor(((float)descRect.height() / (float)painter->fontMetrics().lineSpacing())); if (numLines > 0) { int elideWidth = 0; QTextLayout textLayout(mask->description()); textLayout.beginLayout(); for (int i = 0; i < numLines; i++) { QTextLine line = textLayout.createLine(); if (line.isValid()) { line.setLineWidth(descRect.width()); elideWidth += line.naturalTextWidth(); } } QString elidedText = painter->fontMetrics().elidedText(mask->description(), Qt::ElideRight, elideWidth); painter->drawText(descRect, Qt::AlignLeft|Qt::TextWordWrap, elidedText); } }*/ } painter->restore(); } KisGamutMaskChooser::KisGamutMaskChooser(QWidget *parent) : QWidget(parent) { m_delegate = new KisGamutMaskDelegate(this); m_itemChooser = new KisResourceItemChooser(ResourceType::GamutMasks, false, this); m_itemChooser->setItemDelegate(m_delegate); m_itemChooser->showTaggingBar(true); m_itemChooser->showButtons(false); m_itemChooser->setSynced(true); QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0,0,0,0); // TODO: menu for view mode change QMenu* menu = new QMenu(this); menu->setStyleSheet("margin: 6px"); menu->addSection(i18nc("@title Which elements to display (e.g., thumbnails or details)", "Display")); QActionGroup *actionGroup = new QActionGroup(this); KisConfig cfg(true); m_mode = KisGamutMaskChooser::ViewMode(cfg.readEntry("GamutMasks.viewMode", KisGamutMaskChooser::THUMBNAIL)); QAction* action = menu->addAction(KisIconUtils::loadIcon("view-preview"), i18n("Thumbnails"), this, SLOT(slotSetModeThumbnail())); action->setCheckable(true); action->setChecked(m_mode == KisGamutMaskChooser::THUMBNAIL); action->setActionGroup(actionGroup); action = menu->addAction(KisIconUtils::loadIcon("view-list-details"), i18n("Details"), this, SLOT(slotSetModeDetail())); action->setCheckable(true); action->setChecked(m_mode == KisGamutMaskChooser::DETAIL); action->setActionGroup(actionGroup); // setting the view mode setViewMode(m_mode); m_itemChooser->setViewModeButtonVisible(true); QToolButton* viewModeButton = m_itemChooser->viewModeButton(); viewModeButton->setMenu(menu); layout->addWidget(m_itemChooser); setLayout(layout); connect(m_itemChooser, SIGNAL(resourceSelected(KoResourceSP )), this, SLOT(resourceSelected(KoResourceSP ))); } KisGamutMaskChooser::~KisGamutMaskChooser() { } void KisGamutMaskChooser::setCurrentResource(KoResourceSP resource) { m_itemChooser->setCurrentResource(resource); } void KisGamutMaskChooser::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); updateViewSettings(); } void KisGamutMaskChooser::setViewMode(KisGamutMaskChooser::ViewMode mode) { m_mode = mode; updateViewSettings(); } void KisGamutMaskChooser::updateViewSettings() { KisConfig cfg(false); cfg.writeEntry("GamutMasks.viewMode", qint32(m_mode)); if (m_mode == KisGamutMaskChooser::THUMBNAIL) { m_itemChooser->setSynced(true); m_delegate->setViewMode(m_mode); } else if (m_mode == KisGamutMaskChooser::DETAIL) { m_itemChooser->setSynced(false); m_itemChooser->itemView()->setViewMode(QListView::ListMode); m_itemChooser->setRowHeight(this->fontMetrics().lineSpacing()*4); m_itemChooser->setColumnWidth(m_itemChooser->width()); m_delegate->setViewMode(m_mode); } } void KisGamutMaskChooser::resourceSelected(KoResourceSP resource) { emit sigGamutMaskSelected(resource.staticCast()); } void KisGamutMaskChooser::slotSetModeThumbnail() { setViewMode(KisGamutMaskChooser::THUMBNAIL); } void KisGamutMaskChooser::slotSetModeDetail() { setViewMode(KisGamutMaskChooser::DETAIL); } diff --git a/plugins/dockers/palettedocker/palettedocker_dock.cpp b/plugins/dockers/palettedocker/palettedocker_dock.cpp index baaab588a2..b66f339432 100644 --- a/plugins/dockers/palettedocker/palettedocker_dock.cpp +++ b/plugins/dockers/palettedocker/palettedocker_dock.cpp @@ -1,401 +1,392 @@ /* * Copyright (c) 2013 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "palettedocker_dock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include #include "ui_wdgpalettedock.h" PaletteDockerDock::PaletteDockerDock( ) : QDockWidget(i18n("Palette")) , m_ui(new Ui_WdgPaletteDock()) , m_model(new KisPaletteModel(this)) - , m_paletteChooser(new KisPaletteListWidget(this)) + , m_paletteChooser(new KisPaletteChooser(this)) , m_view(0) , m_resourceProvider(0) , m_rServer(KoResourceServerProvider::instance()->paletteServer()) , m_activeDocument(0) , m_paletteEditor(new KisPaletteEditor) , m_actAdd(new QAction(KisIconUtils::loadIcon("list-add"), i18n("Add a color"))) , m_actRemove(new QAction(KisIconUtils::loadIcon("edit-delete"), i18n("Delete color"))) , m_actModify(new QAction(KisIconUtils::loadIcon("edit-rename"), i18n("Modify this spot"))) , m_actEditPalette(new QAction(KisIconUtils::loadIcon("groupLayer"), i18n("Edit this palette"))) , m_colorSelfUpdate(false) { QWidget *mainWidget = new QWidget(this); setWidget(mainWidget); m_ui->setupUi(mainWidget); m_ui->bnAdd->setDefaultAction(m_actAdd.data()); m_ui->bnRemove->setDefaultAction(m_actRemove.data()); m_ui->bnRename->setDefaultAction(m_actModify.data()); m_ui->bnEditPalette->setDefaultAction(m_actEditPalette.data()); // to make sure their icons have the same size m_ui->bnRemove->setIconSize(QSize(16, 16)); m_ui->bnRename->setIconSize(QSize(16, 16)); m_ui->bnAdd->setIconSize(QSize(16, 16)); m_ui->bnEditPalette->setIconSize(QSize(16, 16)); m_ui->paletteView->setPaletteModel(m_model); m_ui->paletteView->setAllowModification(true); m_ui->cmbNameList->setCompanionView(m_ui->paletteView); m_paletteEditor->setPaletteModel(m_model); connect(m_actAdd.data(), SIGNAL(triggered()), SLOT(slotAddColor())); connect(m_actRemove.data(), SIGNAL(triggered()), SLOT(slotRemoveColor())); connect(m_actModify.data(), SIGNAL(triggered()), SLOT(slotEditEntry())); connect(m_actEditPalette.data(), SIGNAL(triggered()), SLOT(slotEditPalette())); connect(m_ui->paletteView, SIGNAL(sigIndexSelected(QModelIndex)), SLOT(slotPaletteIndexSelected(QModelIndex))); connect(m_ui->paletteView, SIGNAL(clicked(QModelIndex)), SLOT(slotPaletteIndexClicked(QModelIndex))); connect(m_ui->paletteView, SIGNAL(doubleClicked(QModelIndex)), SLOT(slotPaletteIndexDoubleClicked(QModelIndex))); connect(m_ui->cmbNameList, SIGNAL(sigColorSelected(const KoColor&)), SLOT(slotNameListSelection(const KoColor&))); m_viewContextMenu.addAction(m_actModify.data()); m_viewContextMenu.addAction(m_actRemove.data()); connect(m_ui->paletteView, SIGNAL(pressed(QModelIndex)), SLOT(slotContextMenu(QModelIndex))); m_paletteChooser->setAllowModification(true); connect(m_paletteChooser, SIGNAL(sigPaletteSelected(KoColorSetSP)), SLOT(slotSetColorSet(KoColorSetSP))); connect(m_paletteChooser, SIGNAL(sigAddPalette()), SLOT(slotAddPalette())); connect(m_paletteChooser, SIGNAL(sigImportPalette()), SLOT(slotImportPalette())); connect(m_paletteChooser, SIGNAL(sigRemovePalette(KoColorSetSP)), SLOT(slotRemovePalette(KoColorSetSP))); connect(m_paletteChooser, SIGNAL(sigExportPalette(KoColorSetSP)), SLOT(slotExportPalette(KoColorSetSP))); m_ui->bnColorSets->setIcon(KisIconUtils::loadIcon("hi16-palette_library")); m_ui->bnColorSets->setToolTip(i18n("Choose palette")); m_ui->bnColorSets->setPopupWidget(m_paletteChooser); KisConfig cfg(true); QString defaultPaletteName = cfg.defaultPalette(); KoColorSetSP defaultPalette = m_rServer->resourceByName(defaultPaletteName); if (defaultPalette) { slotSetColorSet(defaultPalette); } else { m_ui->bnAdd->setEnabled(false); m_ui->bnRename->setEnabled(false); m_ui->bnRemove->setEnabled(false); m_ui->bnEditPalette->setEnabled(false); m_ui->paletteView->setAllowModification(false); } } PaletteDockerDock::~PaletteDockerDock() { } void PaletteDockerDock::setViewManager(KisViewManager* kisview) { m_view = kisview; m_resourceProvider = kisview->canvasResourceProvider(); connect(m_resourceProvider, SIGNAL(sigSavingWorkspace(KisWorkspaceResourceSP)), SLOT(saveToWorkspace(KisWorkspaceResourceSP))); connect(m_resourceProvider, SIGNAL(sigLoadingWorkspace(KisWorkspaceResourceSP)), SLOT(loadFromWorkspace(KisWorkspaceResourceSP))); connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)), this, SLOT(slotFGColorResourceChanged(KoColor))); kisview->nodeManager()->disconnect(m_model); } void PaletteDockerDock::slotContextMenu(const QModelIndex &) { if (QApplication::mouseButtons() == Qt::RightButton) { m_viewContextMenu.exec(QCursor::pos()); } } void PaletteDockerDock::slotAddPalette() { m_paletteEditor->addPalette(); } void PaletteDockerDock::slotRemovePalette(KoColorSetSP cs) { m_paletteEditor->removePalette(cs); } void PaletteDockerDock::slotImportPalette() { m_paletteEditor->importPalette(); } void PaletteDockerDock::slotExportPalette(KoColorSetSP palette) { KoFileDialog dialog(this, KoFileDialog::SaveFile, "Save Palette"); dialog.setDefaultDir(palette->filename()); dialog.setMimeTypeFilters(QStringList() << "krita/x-colorset"); QString newPath; - bool isStandAlone = palette->isGlobal(); QString oriPath = palette->filename(); if ((newPath = dialog.filename()).isEmpty()) { return; } palette->setFilename(newPath); - palette->setIsGlobal(true); palette->save(); palette->setFilename(oriPath); - palette->setIsGlobal(isStandAlone); } void PaletteDockerDock::setCanvas(KoCanvasBase *canvas) { setEnabled(canvas != 0); if (canvas) { KisCanvas2 *cv = qobject_cast(canvas); m_ui->paletteView->setDisplayRenderer(cv->displayColorConverter()->displayRendererInterface()); } if (m_activeDocument) { m_connections.clear(); for (KoColorSetSP &cs : m_activeDocument->paletteList()) { KoColorSetSP tmpAddr = cs; cs = KoColorSetSP(new KoColorSet(*cs)); m_rServer->removeResourceFromServer(tmpAddr); } } if (m_view && m_view->document()) { m_activeDocument = m_view->document(); m_paletteEditor->setView(m_view); for (KoColorSetSP cs : m_activeDocument->paletteList()) { m_rServer->addResource(cs); } m_connections.addConnection(m_activeDocument, &KisDocument::sigPaletteListChanged, this, &PaletteDockerDock::slotUpdatePaletteList); } if (!m_currentColorSet) { slotSetColorSet(0); } } void PaletteDockerDock::unsetCanvas() { setEnabled(false); m_ui->paletteView->setDisplayRenderer(0); m_paletteEditor->setView(0); - for (KoResourceSP r : m_rServer->resources()) { - KoColorSetSP c = r.staticCast(); - if (!c->isGlobal()) { - m_rServer->removeResourceFromServer(c); - } - } if (!m_currentColorSet) { slotSetColorSet(0); } } void PaletteDockerDock::slotSetColorSet(KoColorSetSP colorSet) { if (colorSet && colorSet->isEditable()) { m_ui->bnAdd->setEnabled(true); m_ui->bnRename->setEnabled(true); m_ui->bnRemove->setEnabled(true); m_ui->bnEditPalette->setEnabled(true); m_ui->paletteView->setAllowModification(true); } else { m_ui->bnAdd->setEnabled(false); m_ui->bnRename->setEnabled(false); m_ui->bnRemove->setEnabled(false); m_ui->bnEditPalette->setEnabled(false); m_ui->paletteView->setAllowModification(false); } m_currentColorSet = colorSet; m_model->setPalette(colorSet); if (colorSet) { KisConfig cfg(true); cfg.setDefaultPalette(colorSet->name()); m_ui->lblPaletteName->setTextElideMode(Qt::ElideLeft); m_ui->lblPaletteName->setText(colorSet->name()); } else { m_ui->lblPaletteName->setText(""); } } void PaletteDockerDock::slotEditPalette() { KisDlgPaletteEditor dlg; if (!m_currentColorSet) { return; } dlg.setPaletteModel(m_model); dlg.setView(m_view); if (dlg.exec() != QDialog::Accepted){ return; } slotSetColorSet(m_currentColorSet); // update GUI } void PaletteDockerDock::slotAddColor() { if (m_resourceProvider) { m_paletteEditor->addEntry(m_resourceProvider->fgColor()); } } void PaletteDockerDock::slotRemoveColor() { QModelIndex index = m_ui->paletteView->currentIndex(); if (!index.isValid()) { return; } m_paletteEditor->removeEntry(index); m_ui->bnRemove->setEnabled(false); } void PaletteDockerDock::setFGColorByPalette(const KisSwatch &entry) { if (m_resourceProvider) { m_colorSelfUpdate = true; m_resourceProvider->setFGColor(entry.color()); m_colorSelfUpdate = false; } } void PaletteDockerDock::saveToWorkspace(KisWorkspaceResourceSP workspace) { if (!m_currentColorSet.isNull()) { workspace->setProperty("palette", m_currentColorSet->name()); } } void PaletteDockerDock::loadFromWorkspace(KisWorkspaceResourceSP workspace) { if (workspace->hasProperty("palette")) { KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); KoColorSetSP colorSet = rServer->resourceByName(workspace->getString("palette")); if (colorSet) { slotSetColorSet(colorSet); } } } void PaletteDockerDock::slotFGColorResourceChanged(const KoColor &color) { if (!m_colorSelfUpdate) { m_ui->paletteView->slotFGColorChanged(color); } } void PaletteDockerDock::slotPaletteIndexSelected(const QModelIndex &index) { bool occupied = qvariant_cast(index.data(KisPaletteModel::CheckSlotRole)); if (occupied) { if (!qvariant_cast(index.data(KisPaletteModel::IsGroupNameRole))) { m_ui->bnRemove->setEnabled(true); KisSwatch entry = m_model->getEntry(index); setFGColorByPalette(entry); } } if (!m_currentColorSet->isEditable()) { return; } m_ui->bnRemove->setEnabled(occupied); } void PaletteDockerDock::slotPaletteIndexClicked(const QModelIndex &index) { if (!(qvariant_cast(index.data(KisPaletteModel::CheckSlotRole)))) { setEntryByForeground(index); } } void PaletteDockerDock::slotPaletteIndexDoubleClicked(const QModelIndex &index) { m_paletteEditor->modifyEntry(index); } void PaletteDockerDock::setEntryByForeground(const QModelIndex &index) { m_paletteEditor->setEntry(m_resourceProvider->fgColor(), index); if (m_currentColorSet->isEditable()) { m_ui->bnRemove->setEnabled(true); } } void PaletteDockerDock::slotEditEntry() { QModelIndex index = m_ui->paletteView->currentIndex(); if (!index.isValid()) { return; } m_paletteEditor->modifyEntry(index); } void PaletteDockerDock::slotNameListSelection(const KoColor &color) { m_colorSelfUpdate = true; m_ui->paletteView->selectClosestColor(color); m_resourceProvider->setFGColor(color); m_colorSelfUpdate = false; } void PaletteDockerDock::slotUpdatePaletteList(const QList &oldPaletteList, const QList &newPaletteList) { for (KoColorSetSP cs : oldPaletteList) { m_rServer->removeResourceFromServer(cs); } for (KoColorSetSP cs : newPaletteList) { m_rServer->addResource(cs); } if (!m_currentColorSet) { slotSetColorSet(0); } } diff --git a/plugins/dockers/palettedocker/palettedocker_dock.h b/plugins/dockers/palettedocker/palettedocker_dock.h index ada1517912..97946950f2 100644 --- a/plugins/dockers/palettedocker/palettedocker_dock.h +++ b/plugins/dockers/palettedocker/palettedocker_dock.h @@ -1,120 +1,120 @@ /* * Copyright (c) 2013 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PALETTEDOCKER_DOCK_H #define PALETTEDOCKER_DOCK_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KisViewManager; class KisCanvasResourceProvider; -class KisPaletteListWidget; +class KisPaletteChooser; class KisPaletteModel; class KisPaletteEditor; class Ui_WdgPaletteDock; class PaletteDockerDock : public QDockWidget, public KisMainwindowObserver { Q_OBJECT public: PaletteDockerDock(); ~PaletteDockerDock() override; public: // QDockWidget void setCanvas(KoCanvasBase *canvas) override; void unsetCanvas() override; public: // KisMainWindowObserver void setViewManager(KisViewManager* kisview) override; private Q_SLOTS: void slotContextMenu(const QModelIndex &); void slotAddPalette(); void slotRemovePalette(KoColorSetSP ); void slotImportPalette(); void slotExportPalette(KoColorSetSP ); void slotAddColor(); void slotRemoveColor(); void slotEditEntry(); void slotEditPalette(); void slotPaletteIndexSelected(const QModelIndex &index); void slotPaletteIndexClicked(const QModelIndex &index); void slotPaletteIndexDoubleClicked(const QModelIndex &index); void slotNameListSelection(const KoColor &color); void slotSetColorSet(KoColorSetSP colorSet); void saveToWorkspace(KisWorkspaceResourceSP workspace); void loadFromWorkspace(KisWorkspaceResourceSP workspace); void slotFGColorResourceChanged(const KoColor& color); void slotUpdatePaletteList(const QList &oldPaletteList, const QList &newPaletteList); private: void setEntryByForeground(const QModelIndex &index); void setFGColorByPalette(const KisSwatch &entry); private /* member variables */: QScopedPointer m_ui; KisPaletteModel *m_model; - KisPaletteListWidget *m_paletteChooser; + KisPaletteChooser *m_paletteChooser; QPointer m_view; KisCanvasResourceProvider *m_resourceProvider; KoResourceServer * const m_rServer; QPointer m_activeDocument; QSharedPointer m_currentColorSet; QScopedPointer m_paletteEditor; QScopedPointer m_actAdd; QScopedPointer m_actRemove; QScopedPointer m_actModify; QScopedPointer m_actEditPalette; QMenu m_viewContextMenu; bool m_colorSelfUpdate; KisSignalAutoConnectionsStore m_connections; }; #endif diff --git a/plugins/extensions/layersplit/dlg_layersplit.cpp b/plugins/extensions/layersplit/dlg_layersplit.cpp index fa12ac8b2f..60cbacf586 100644 --- a/plugins/extensions/layersplit/dlg_layersplit.cpp +++ b/plugins/extensions/layersplit/dlg_layersplit.cpp @@ -1,160 +1,160 @@ /* * 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 "dlg_layersplit.h" #include #include #include #include #include #include #include "kis_slider_spin_box.h" #include #include #include #include DlgLayerSplit::DlgLayerSplit() : KoDialog() { m_page = new WdgLayerSplit(this); setCaption(i18n("Split Layer")); setButtons(Apply | Cancel); setDefaultButton(Apply); m_page->intFuzziness->setRange(0, 200); m_page->intFuzziness->setSingleStep(1); - m_colorSetChooser = new KisPaletteListWidget(); + m_colorSetChooser = new KisPaletteChooser(); m_page->paletteChooser->setPopupWidget(m_colorSetChooser); connect(m_colorSetChooser, SIGNAL(sigPaletteSelected(KoColorSetSP)), this, SLOT(slotSetPalette(KoColorSetSP))); KisDialogStateSaver::restoreState(m_page, "krita/layer_split"); connect(m_page->cmbMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChangeMode(int))); KisConfig cfg(true); QString paletteName = cfg.readEntry("layersplit/paletteName", i18n("Default")); KoResourceServer *pserver = KoResourceServerProvider::instance()->paletteServer(); KoColorSetSP pal = pserver->resourceByName(paletteName); m_modeToMask = m_page->cmbMode->currentIndex(); slotChangeMode(m_modeToMask); if (pal) { m_palette = pal; m_page->paletteChooser->setText(pal->name()); QIcon icon(QPixmap::fromImage(pal->image())); m_page->paletteChooser->setIcon(icon); } connect(this, SIGNAL(applyClicked()), this, SLOT(slotApplyClicked())); setMainWidget(m_page); } DlgLayerSplit::~DlgLayerSplit() { } void DlgLayerSplit::slotApplyClicked() { KisDialogStateSaver::saveState(m_page, "krita/layer_split"); KisConfig cfg(false); if (m_palette) { cfg.writeEntry("layersplit/paletteName", m_palette->name()); } accept(); } bool DlgLayerSplit::createBaseGroup() const { return m_page->chkCreateGroupLayer->isChecked(); } bool DlgLayerSplit::createSeparateGroups() const { return m_page->chkSeparateGroupLayers->isChecked(); } bool DlgLayerSplit::lockAlpha() const { return m_page->chkAlphaLock->isChecked(); } bool DlgLayerSplit::hideOriginal() const { return m_page->chkHideOriginal->isChecked(); } bool DlgLayerSplit::sortLayers() const { return m_page->chkSortLayers->isChecked(); } bool DlgLayerSplit::disregardOpacity() const { return m_page->chkDisregardOpacity->isChecked(); } int DlgLayerSplit::fuzziness() const { return m_page->intFuzziness->value(); } KoColorSetSP DlgLayerSplit::palette() const { return m_palette; } void DlgLayerSplit::slotSetPalette(KoColorSetSP pal) { if (pal) { m_palette = pal; m_page->paletteChooser->setText(pal->name()); QIcon icon(QPixmap::fromImage(pal->image())); m_page->paletteChooser->setIcon(icon); } } void DlgLayerSplit::slotChangeMode(int idx){ m_modeToMask = idx; if( m_modeToMask){ m_page->chkCreateGroupLayer->hide(); m_page->chkSeparateGroupLayers->hide(); m_page->chkAlphaLock->hide(); m_page->chkHideOriginal->hide(); } else{ m_page->chkCreateGroupLayer->show(); m_page->chkSeparateGroupLayers->show(); m_page->chkAlphaLock->show(); m_page->chkHideOriginal->show(); } } diff --git a/plugins/extensions/layersplit/dlg_layersplit.h b/plugins/extensions/layersplit/dlg_layersplit.h index 7d6e897d71..fdcf29767b 100644 --- a/plugins/extensions/layersplit/dlg_layersplit.h +++ b/plugins/extensions/layersplit/dlg_layersplit.h @@ -1,68 +1,68 @@ /* * 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 DLG_LAYERSPLIT #define DLG_LAYERSPLIT #include #include -#include +#include #include #include "wdg_layersplit.h" /** * This dialog allows the user to create a selection mask based * on a (range of) colors. */ class DlgLayerSplit: public KoDialog { Q_OBJECT public: DlgLayerSplit(); ~DlgLayerSplit() override; bool createBaseGroup() const; bool createSeparateGroups() const; bool lockAlpha() const; bool hideOriginal() const; bool sortLayers() const; bool disregardOpacity() const; int fuzziness() const; KoColorSetSP palette() const; private Q_SLOTS: void slotApplyClicked(); void slotSetPalette(KoColorSetSP pal); void slotChangeMode(int); private: friend class LayerSplit; bool m_modeToMask; WdgLayerSplit *m_page {0}; - KisPaletteListWidget *m_colorSetChooser {0}; + KisPaletteChooser *m_colorSetChooser {0}; KoColorSetSP m_palette {0}; }; #endif // DLG_LAYERSPLIT diff --git a/plugins/extensions/pykrita/kritarunner/main.cpp b/plugins/extensions/pykrita/kritarunner/main.cpp index 07791e2c79..7aaeffaa59 100644 --- a/plugins/extensions/pykrita/kritarunner/main.cpp +++ b/plugins/extensions/pykrita/kritarunner/main.cpp @@ -1,129 +1,126 @@ /* * Copyright (c) 2016 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 "kis_md5_generator.h" #include "PythonPluginManager.h" #include extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); KLocalizedString::setApplicationDomain("kritarunner"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); KisOpenGL::testingInitializeDefaultSurfaceFormat(); // first create the application so we can create a pixmap KisApplication app("kritarunner", argc, argv); app.setApplicationDisplayName("Krita Script Runner"); app.setApplicationName("kritarunner"); app.setOrganizationDomain("krita.org"); QCommandLineParser parser; parser.setApplicationDescription("kritarunner executes one python script and then returns."); parser.addVersionOption(); parser.addHelpOption(); QCommandLineOption scriptOption(QStringList() << "s" << "script", "The script to run. Do not append the .py extension.", "script"); parser.addOption(scriptOption); QCommandLineOption functionOption(QStringList() << "f" << "function", "The function to call (by default __main__ is called).", "function", "__main__"); parser.addOption(functionOption); parser.addPositionalArgument("[argument(s)]", "The arguments for the script"); parser.process(app); if (!parser.isSet(scriptOption)) { qDebug("No script given, aborting."); return 1; } qDebug() << "running:" << parser.value(scriptOption) << parser.value(functionOption); qDebug() << parser.positionalArguments(); - KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); app.addResourceTypes(); app.registerResources(); app.loadPlugins(); QByteArray pythonPath = qgetenv("PYTHONPATH"); qDebug() << "\tPython path:" << pythonPath; qDebug() << "Creating engine"; // TODO: refactor to share common parts with plugin.cpp PyKrita::InitResult initResult = PyKrita::initialize(); switch (initResult) { case PyKrita::INIT_OK: break; case PyKrita::INIT_CANNOT_LOAD_PYTHON_LIBRARY: qWarning() << i18n("Cannot load Python library"); return 1; case PyKrita::INIT_CANNOT_SET_PYTHON_PATHS: qWarning() << i18n("Cannot set Python paths"); return 1; case PyKrita::INIT_CANNOT_LOAD_PYKRITA_MODULE: qWarning() << i18n("Cannot load built-in pykrita module"); return 1; default: qWarning() << i18n("Unexpected error initializing python plugin."); return 1; } qDebug() << "Try to import the pykrita module"; PyKrita::Python py = PyKrita::Python(); PyObject* pykritaPackage = py.moduleImport("pykrita"); pykritaPackage = py.moduleImport("krita"); if (!pykritaPackage) { qDebug("Cannot load the PyKrita module, aborting"); return 1; } PyObject *argsList = PyList_New(0); Q_FOREACH(const QString arg, parser.positionalArguments()) { PyObject* const u = py.unicode(arg); PyList_Append(argsList, u); Py_DECREF(u); } PyObject *args = PyTuple_New(1); PyTuple_SetItem(args, 0, argsList); py.functionCall(parser.value(functionOption).toUtf8().constData(), parser.value(scriptOption).toUtf8().constData(), args); Py_DECREF(argsList); Py_DECREF(args); app.quit(); return 0; } diff --git a/plugins/extensions/resourcemanager/dlg_bundle_manager.cpp b/plugins/extensions/resourcemanager/dlg_bundle_manager.cpp index 2b9f2c5051..066172b469 100644 --- a/plugins/extensions/resourcemanager/dlg_bundle_manager.cpp +++ b/plugins/extensions/resourcemanager/dlg_bundle_manager.cpp @@ -1,79 +1,87 @@ /* * Copyright (c) 2014 Victor Lafon metabolic.ewilan@hotmail.fr * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "dlg_bundle_manager.h" #include "ui_wdgdlgbundlemanager.h" #include "resourcemanager.h" #include "dlg_create_bundle.h" #include #include #include #include #include #include #include #include "kis_action.h" +#include #include #include #include #include #include +#include DlgBundleManager::DlgBundleManager(QWidget *parent) : KoDialog(parent) , m_page(new QWidget()) , m_ui(new Ui::WdgDlgBundleManager) { setCaption(i18n("Manage Resource Libraries")); m_ui->setupUi(m_page); setMainWidget(m_page); resize(m_page->sizeHint()); m_ui->bnAdd->setIcon(KisIconUtils::loadIcon("list-add")); connect(m_ui->bnAdd, SIGNAL(clicked(bool)), SLOT(addBundle())); m_ui->bnNew->setIcon(KisIconUtils::loadIcon("document-new")); connect(m_ui->bnNew, SIGNAL(clicked(bool)), SLOT(createBundle())); m_ui->bnDelete->setIcon(KisIconUtils::loadIcon("edit-delete")); connect(m_ui->bnDelete, SIGNAL(clicked(bool)), SLOT(deleteBundle())); setButtons(Close); - m_ui->tableView->setModel(KisStorageModel::instance()); + KisStorageFilterProxyModel *proxyModel = new KisStorageFilterProxyModel(this); + proxyModel->setSourceModel(KisStorageModel::instance()); + proxyModel->setFilter(KisStorageFilterProxyModel::ByStorageType, + QStringList() + << KisResourceStorage::storageTypeToUntranslatedString(KisResourceStorage::StorageType::Bundle) + << KisResourceStorage::storageTypeToUntranslatedString(KisResourceStorage::StorageType::Folder)); + m_ui->tableView->setModel(proxyModel); } void DlgBundleManager::addBundle() { } void DlgBundleManager::createBundle() { } void DlgBundleManager::deleteBundle() { } diff --git a/plugins/extensions/resourcemanager/dlg_create_bundle.cpp b/plugins/extensions/resourcemanager/dlg_create_bundle.cpp index 45d5bb3332..b0564841c6 100644 --- a/plugins/extensions/resourcemanager/dlg_create_bundle.cpp +++ b/plugins/extensions/resourcemanager/dlg_create_bundle.cpp @@ -1,412 +1,412 @@ /* * Copyright (c) 2014 Victor Lafon metabolic.ewilan@hotmail.fr * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "dlg_create_bundle.h" #include "ui_wdgdlgcreatebundle.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ICON_SIZE 48 DlgCreateBundle::DlgCreateBundle(KoResourceBundleSP bundle, QWidget *parent) : KoDialog(parent) , m_ui(new Ui::WdgDlgCreateBundle) , m_bundle(bundle) { m_page = new QWidget(); m_ui->setupUi(m_page); setMainWidget(m_page); setFixedSize(m_page->sizeHint()); setButtons(Ok | Cancel); setDefaultButton(Ok); setButtonText(Ok, i18n("Save")); connect(m_ui->bnSelectSaveLocation, SIGNAL(clicked()), SLOT(selectSaveLocation())); KoDocumentInfo info; info.updateParameters(); if (bundle) { setCaption(i18n("Edit Resource Bundle")); #if 0 m_ui->lblSaveLocation->setText(QFileInfo(bundle->filename()).absolutePath()); m_ui->editBundleName->setText(bundle->name()); m_ui->editAuthor->setText(bundle->getMeta("author")); m_ui->editEmail->setText(bundle->getMeta("email")); m_ui->editLicense->setText(bundle->getMeta("license")); m_ui->editWebsite->setText(bundle->getMeta("website")); m_ui->editDescription->document()->setPlainText(bundle->getMeta("description")); m_ui->lblPreview->setPixmap(QPixmap::fromImage(bundle->image().scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation))); Q_FOREACH (const QString & resType, bundle->resourceTypes()) { if (resType == ResourceType::Gradients) { Q_FOREACH (const KoResourceSP res, bundle->resources(resType)) { if (res) { m_selectedGradients << res->filename(); } } } else if (resType == ResourceType::Patterns) { Q_FOREACH (const KoResourceSP res, bundle->resources(resType)) { if (res) { m_selectedPatterns << res->filename(); } } } else if (resType == ResourceType::Brushes) { Q_FOREACH (const KoResourceSP res, bundle->resources(resType)) { if (res) { m_selectedBrushes << res->filename(); } } } else if (resType == ResourceType::Palettes) { Q_FOREACH (const KoResourceSP res, bundle->resources(resType)) { if (res) { m_selectedPalettes << res->filename(); } } } else if (resType == ResourceType::Workspaces) { Q_FOREACH (const KoResourceSP res, bundle->resources(resType)) { if (res) { m_selectedWorkspaces << res->filename(); } } } else if (resType == ResourceType::PaintOpPresets) { Q_FOREACH (const KoResourceSP res, bundle->resources(resType)) { if (res) { m_selectedPresets << res->filename(); } } } else if (resType == ResourceType::GamutMasks) { Q_FOREACH (const KoResourceSP res, bundle->resources(resType)) { if (res) { m_selectedGamutMasks << res->filename(); } } } } #endif } else { setCaption(i18n("Create Resource Bundle")); KisConfig cfg(true); m_ui->editAuthor->setText(cfg.readEntry("BundleAuthorName", info.authorInfo("creator"))); m_ui->editEmail->setText(cfg.readEntry("BundleAuthorEmail", info.authorInfo("email"))); m_ui->editWebsite->setText(cfg.readEntry("BundleWebsite", "http://")); m_ui->editLicense->setText(cfg.readEntry("BundleLicense", "CC-BY-SA")); m_ui->lblSaveLocation->setText(cfg.readEntry("BundleExportLocation", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation))); } m_ui->bnAdd->setIcon(KisIconUtils::loadIcon("arrow-right")); connect(m_ui->bnAdd, SIGNAL(clicked()), SLOT(addSelected())); m_ui->bnRemove->setIcon(KisIconUtils::loadIcon("arrow-left")); connect(m_ui->bnRemove, SIGNAL(clicked()), SLOT(removeSelected())); m_ui->cmbResourceTypes->addItem(i18n("Brushes"), ResourceType::Brushes); m_ui->cmbResourceTypes->addItem(i18n("Brush Presets"), ResourceType::PaintOpPresets); m_ui->cmbResourceTypes->addItem(i18n("Gradients"), ResourceType::Gradients); m_ui->cmbResourceTypes->addItem(i18n("Gamut Masks"), ResourceType::GamutMasks); m_ui->cmbResourceTypes->addItem(i18n("Patterns"), ResourceType::Patterns); m_ui->cmbResourceTypes->addItem(i18n("Palettes"), ResourceType::Palettes); m_ui->cmbResourceTypes->addItem(i18n("Workspaces"), ResourceType::Workspaces); connect(m_ui->cmbResourceTypes, SIGNAL(activated(int)), SLOT(resourceTypeSelected(int))); m_ui->tableAvailable->setIconSize(QSize(ICON_SIZE, ICON_SIZE)); m_ui->tableAvailable->setSelectionMode(QAbstractItemView::ExtendedSelection); m_ui->tableSelected->setIconSize(QSize(ICON_SIZE, ICON_SIZE)); m_ui->tableSelected->setSelectionMode(QAbstractItemView::ExtendedSelection); connect(m_ui->bnGetPreview, SIGNAL(clicked()), SLOT(getPreviewImage())); resourceTypeSelected(0); } DlgCreateBundle::~DlgCreateBundle() { delete m_ui; } QString DlgCreateBundle::bundleName() const { return m_ui->editBundleName->text().replace(" ", "_"); } QString DlgCreateBundle::authorName() const { return m_ui->editAuthor->text(); } QString DlgCreateBundle::email() const { return m_ui->editEmail->text(); } QString DlgCreateBundle::website() const { return m_ui->editWebsite->text(); } QString DlgCreateBundle::license() const { return m_ui->editLicense->text(); } QString DlgCreateBundle::description() const { return m_ui->editDescription->document()->toPlainText(); } QString DlgCreateBundle::saveLocation() const { return m_ui->lblSaveLocation->text(); } QString DlgCreateBundle::previewImage() const { return m_previewImage; } void DlgCreateBundle::accept() { QString name = m_ui->editBundleName->text().remove(" "); if (name.isEmpty()) { m_ui->editBundleName->setStyleSheet(QString(" border: 1px solid red")); QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The resource bundle name cannot be empty.")); return; } else { QFileInfo fileInfo(m_ui->lblSaveLocation->text() + "/" + name + ".bundle"); if (fileInfo.exists() && !m_bundle) { m_ui->editBundleName->setStyleSheet("border: 1px solid red"); QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("A bundle with this name already exists.")); return; } else { if (!m_bundle) { KisConfig cfg(false); cfg.writeEntry("BunleExportLocation", m_ui->lblSaveLocation->text()); cfg.writeEntry("BundleAuthorName", m_ui->editAuthor->text()); cfg.writeEntry("BundleAuthorEmail", m_ui->editEmail->text()); cfg.writeEntry("BundleWebsite", m_ui->editWebsite->text()); cfg.writeEntry("BundleLicense", m_ui->editLicense->text()); } KoDialog::accept(); } } } void DlgCreateBundle::selectSaveLocation() { KoFileDialog dialog(this, KoFileDialog::OpenDirectory, "resourcebundlesavelocation"); dialog.setDefaultDir(m_ui->lblSaveLocation->text()); dialog.setCaption(i18n("Select a directory to save the bundle")); QString location = dialog.filename(); m_ui->lblSaveLocation->setText(location); } void DlgCreateBundle::addSelected() { int row = m_ui->tableAvailable->currentRow(); Q_FOREACH (QListWidgetItem *item, m_ui->tableAvailable->selectedItems()) { m_ui->tableSelected->addItem(m_ui->tableAvailable->takeItem(m_ui->tableAvailable->row(item))); QString resourceType = m_ui->cmbResourceTypes->itemData(m_ui->cmbResourceTypes->currentIndex()).toString(); if (resourceType == ResourceType::Brushes) { m_selectedBrushes.append(item->data(Qt::UserRole).toString()); } else if (resourceType == "presets") { m_selectedPresets.append(item->data(Qt::UserRole).toString()); } else if (resourceType == ResourceType::Gradients) { m_selectedGradients.append(item->data(Qt::UserRole).toString()); } else if (resourceType == ResourceType::Patterns) { m_selectedPatterns.append(item->data(Qt::UserRole).toString()); } else if (resourceType == ResourceType::Palettes) { m_selectedPalettes.append(item->data(Qt::UserRole).toString()); } else if (resourceType == ResourceType::Workspaces) { m_selectedWorkspaces.append(item->data(Qt::UserRole).toString()); } else if (resourceType == ResourceType::GamutMasks) { m_selectedGamutMasks.append(item->data(Qt::UserRole).toString()); } } m_ui->tableAvailable->setCurrentRow(row); } void DlgCreateBundle::removeSelected() { int row = m_ui->tableSelected->currentRow(); Q_FOREACH (QListWidgetItem *item, m_ui->tableSelected->selectedItems()) { m_ui->tableAvailable->addItem(m_ui->tableSelected->takeItem(m_ui->tableSelected->row(item))); QString resourceType = m_ui->cmbResourceTypes->itemData(m_ui->cmbResourceTypes->currentIndex()).toString(); if (resourceType == ResourceType::Brushes) { m_selectedBrushes.removeAll(item->data(Qt::UserRole).toString()); } else if (resourceType == "presets") { m_selectedPresets.removeAll(item->data(Qt::UserRole).toString()); } else if (resourceType == ResourceType::Gradients) { m_selectedGradients.removeAll(item->data(Qt::UserRole).toString()); } else if (resourceType == ResourceType::Patterns) { m_selectedPatterns.removeAll(item->data(Qt::UserRole).toString()); } else if (resourceType == ResourceType::Palettes) { m_selectedPalettes.removeAll(item->data(Qt::UserRole).toString()); } else if (resourceType == ResourceType::Workspaces) { m_selectedWorkspaces.removeAll(item->data(Qt::UserRole).toString()); } else if (resourceType == ResourceType::GamutMasks) { m_selectedGamutMasks.removeAll(item->data(Qt::UserRole).toString()); } } m_ui->tableSelected->setCurrentRow(row); } QPixmap imageToIcon(const QImage &img) { QPixmap pixmap(ICON_SIZE, ICON_SIZE); pixmap.fill(); QImage scaled = img.scaled(ICON_SIZE, ICON_SIZE, Qt::KeepAspectRatio, Qt::SmoothTransformation); int x = (ICON_SIZE - scaled.width()) / 2; int y = (ICON_SIZE - scaled.height()) / 2; QPainter gc(&pixmap); gc.drawImage(x, y, scaled); gc.end(); return pixmap; } void DlgCreateBundle::resourceTypeSelected(int idx) { QString resourceType = m_ui->cmbResourceTypes->itemData(idx).toString(); m_ui->tableAvailable->clear(); m_ui->tableSelected->clear(); QString standarizedResourceType = (resourceType == "presets" ? ResourceType::PaintOpPresets : resourceType); QStringList& list = m_selectedBrushes; if (standarizedResourceType == ResourceType::Brushes) { list = m_selectedBrushes; } else if (standarizedResourceType == ResourceType::Gradients) { list = m_selectedGradients; } else if (standarizedResourceType == ResourceType::GamutMasks) { list = m_selectedGamutMasks; } else if (standarizedResourceType == ResourceType::Palettes) { list = m_selectedPalettes; } else if (standarizedResourceType == ResourceType::Patterns) { list = m_selectedPatterns; } else if (standarizedResourceType == ResourceType::PaintOpPresets) { list = m_selectedPresets; } else if (standarizedResourceType == ResourceType::Workspaces) { list = m_selectedWorkspaces; } KisResourceModel* model = KisResourceModelProvider::resourceModel(standarizedResourceType); for (int i = 0; i < model->rowCount(); i++) { QModelIndex idx = model->index(i, 0); QString filename = model->data(idx, Qt::UserRole + KisResourceModel::Filename).toString(); if (resourceType == ResourceType::Gradients) { if (filename == "Foreground to Transparent" || filename == "Foreground to Background") { continue; } } - QImage image = (model->data(idx, Qt::UserRole + KisResourceModel::Image)).value(); + QImage image = (model->data(idx, Qt::UserRole + KisResourceModel::Thumbnail)).value(); QString name = model->data(idx, Qt::UserRole + KisResourceModel::Name).toString(); // Function imageToIcon(QImage()) returns a square white pixmap and a warning "QImage::scaled: Image is a null image" // while QPixmap() returns an empty pixmap. // The difference between them is relevant in case of Workspaces which has no images. // Using QPixmap() makes them appear in a dense list without icons, while imageToIcon(QImage()) // would give a list with big white rectangles and names of the workspaces. QListWidgetItem *item = new QListWidgetItem(image.isNull() ? QPixmap() : imageToIcon(image), name); item->setData(Qt::UserRole, filename); if (list.contains(filename)) { m_ui->tableSelected->addItem(item); } else { m_ui->tableAvailable->addItem(item); } } } void DlgCreateBundle::getPreviewImage() { KoFileDialog dialog(this, KoFileDialog::OpenFile, "BundlePreviewImage"); dialog.setCaption(i18n("Select file to use as bundle icon")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); m_previewImage = dialog.filename(); QImage img(m_previewImage); img = img.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation); m_ui->lblPreview->setPixmap(QPixmap::fromImage(img)); } diff --git a/plugins/impex/libkra/kis_kra_load_visitor.cpp b/plugins/impex/libkra/kis_kra_load_visitor.cpp index d495abd1ff..9210b778d8 100644 --- a/plugins/impex/libkra/kis_kra_load_visitor.cpp +++ b/plugins/impex/libkra/kis_kra_load_visitor.cpp @@ -1,789 +1,788 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_load_visitor.h" #include "kis_kra_tags.h" #include "flake/kis_shape_layer.h" #include "flake/KisReferenceImagesLayer.h" #include "KisReferenceImage.h" #include #include #include #include #include -#include -#include +#include +#include #include #include #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" #include "kis_filter_registry.h" using namespace KRA; QString expandEncodedDirectory(const QString& _intern) { QString intern = _intern; QString result; int pos; while ((pos = intern.indexOf('/')) != -1) { if (QChar(intern.at(0)).isDigit()) result += "part"; result += intern.left(pos + 1); // copy numbers (or "pictures") + "/" intern = intern.mid(pos + 1); // remove the dir we just processed } if (!intern.isEmpty() && QChar(intern.at(0)).isDigit()) result += "part"; result += intern; return result; } KisKraLoadVisitor::KisKraLoadVisitor(KisImageSP image, KoStore *store, KoShapeControllerBase *shapeController, QMap &layerFilenames, QMap &keyframeFilenames, const QString & name, int syntaxVersion) : KisNodeVisitor() , m_image(image) , m_store(store) , m_external(false) , m_layerFilenames(layerFilenames) , m_keyframeFilenames(keyframeFilenames) , m_name(name) , m_shapeController(shapeController) { m_store->pushDirectory(); if (!m_store->enterDirectory(m_name)) { QStringList directories = m_store->directoryList(); dbgKrita << directories; if (directories.size() > 0) { dbgFile << "Could not locate the directory, maybe some encoding issue? Grab the first directory, that'll be the image one." << m_name << directories; m_name = directories.first(); } else { dbgFile << "Could not enter directory" << m_name << ", probably an old-style file with 'part' added."; m_name = expandEncodedDirectory(m_name); } } else { m_store->popDirectory(); } m_syntaxVersion = syntaxVersion; } void KisKraLoadVisitor::setExternalUri(const QString &uri) { m_external = true; m_uri = uri; } bool KisKraLoadVisitor::visit(KisExternalLayer * layer) { bool result = false; if (auto *referencesLayer = dynamic_cast(layer)) { Q_FOREACH(KoShape *shape, referencesLayer->shapes()) { auto *reference = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(reference, false); while (!reference->loadImage(m_store)) { if (reference->embed()) { m_errorMessages << i18n("Could not load embedded reference image %1 ", reference->internalFile()); break; } else { QString msg = i18nc( "@info", "A reference image linked to an external file could not be loaded.\n\n" "Path: %1\n\n" "Do you want to select another location?", reference->filename()); int locateManually = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); QString url; if (locateManually == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); url = dialog.filename(); } if (url.isEmpty()) { break; } else { reference->setFilename(url); } } } } } else if (KisShapeLayer *shapeLayer = dynamic_cast(layer)) { if (!loadMetaData(layer)) { return false; } m_store->pushDirectory(); m_store->enterDirectory(getLocation(layer, DOT_SHAPE_LAYER)) ; result = shapeLayer->loadLayer(m_store); m_store->popDirectory(); } result = visitAll(layer) && result; return result; } bool KisKraLoadVisitor::visit(KisPaintLayer *layer) { loadNodeKeyframes(layer); 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, getLocation(layer, DOT_FILTERCONFIG)); fixOldFilterConfigurations(layer->filter()); 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()); // HACK ALERT: we set the same filter again to ensure the layer // is correctly updated result = loadFilterConfiguration(layer, getLocation(layer, DOT_FILTERCONFIG)); layer->setFilter(layer->filter()); 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()); if (!srcNode.isNull()) { KisLayerSP srcLayer = qobject_cast(srcNode.data()); Q_ASSERT(srcLayer); layer->setCopyFrom(srcLayer); } else { m_warningMessages.append(i18nc("Loading a .kra file", "The file contains a clone layer that has an incorrect source node id. " "This layer will be converted into a paint layer.")); } // 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, getLocation(mask, DOT_FILTERCONFIG)); fixOldFilterConfigurations(mask->filter()); return result; } bool KisKraLoadVisitor::visit(KisTransformMask *mask) { QString location = getLocation(mask, DOT_TRANSFORMCONFIG); if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement rootElement = doc.documentElement(); QDomElement main; if (!KisDomUtils::findOnlyElement(rootElement, "main", &main/*, &m_errorMessages*/)) { return false; } QString id = main.attribute("id", "not-valid"); if (id == "not-valid") { m_errorMessages << i18n("Could not load \"id\" of the transform mask"); return false; } QDomElement data; if (!KisDomUtils::findOnlyElement(rootElement, "data", &data, &m_errorMessages)) { return false; } KisTransformMaskParamsInterfaceSP params = KisTransformMaskParamsFactoryRegistry::instance()->createParams(id, data); if (!params) { m_errorMessages << i18n("Could not create transform mask params"); return false; } mask->setTransformParams(params); loadNodeKeyframes(mask); params->clearChangedFlag(); return true; } } return false; } bool KisKraLoadVisitor::visit(KisTransparencyMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisSelectionMask *mask) { initSelectionForMask(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisColorizeMask *mask) { m_store->pushDirectory(); QString location = getLocation(mask, DOT_COLORIZE_MASK); m_store->enterDirectory(location) ; QByteArray data; if (!m_store->extractFile("content.xml", data)) return false; QDomDocument doc; if (!doc.setContent(data)) return false; QVector strokes; if (!KisDomUtils::loadValue(doc.documentElement(), COLORIZE_KEYSTROKES_SECTION, &strokes, mask->colorSpace())) return false; int i = 0; Q_FOREACH (const KisLazyFillTools::KeyStroke &stroke, strokes) { const QString fileName = QString("%1_%2").arg(COLORIZE_KEYSTROKE).arg(i++); loadPaintDevice(stroke.dev, fileName); } mask->setKeyStrokesDirect(QList::fromVector(strokes)); loadPaintDevice(mask->coloringProjection(), COLORIZE_COLORING_DEVICE); mask->resetCache(); m_store->popDirectory(); return true; } QStringList KisKraLoadVisitor::errorMessages() const { return m_errorMessages; } QStringList KisKraLoadVisitor::warningMessages() const { return m_warningMessages; } struct SimpleDevicePolicy { bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->read(stream); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->setDefaultPixel(defaultPixel); } }; struct FramedDevicePolicy { FramedDevicePolicy(int frameId) : m_frameId(frameId) {} bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->framesInterface()->readFrame(stream, m_frameId); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->framesInterface()->setFrameDefaultPixel(defaultPixel, m_frameId); } int m_frameId; }; bool KisKraLoadVisitor::loadPaintDevice(KisPaintDeviceSP device, const QString& location) { // Layer data KisPaintDeviceFramesInterface *frameInterface = device->framesInterface(); QList frames; if (frameInterface) { frames = device->framesInterface()->frames(); } if (!frameInterface || frames.count() <= 1) { return loadPaintDeviceFrame(device, location, SimpleDevicePolicy()); } else { KisRasterKeyframeChannel *keyframeChannel = device->keyframeChannel(); for (int i = 0; i < frames.count(); i++) { int id = frames[i]; if (keyframeChannel->frameFilename(id).isEmpty()) { m_warningMessages << i18n("Could not find keyframe pixel data for frame %1 in %2.", id, location); } else { Q_ASSERT(!keyframeChannel->frameFilename(id).isEmpty()); QString frameFilename = getLocation(keyframeChannel->frameFilename(id)); Q_ASSERT(!frameFilename.isEmpty()); if (!loadPaintDeviceFrame(device, frameFilename, FramedDevicePolicy(id))) { m_warningMessages << i18n("Could not load keyframe pixel data for frame %1 in %2.", id, location); } } } } return true; } template bool KisKraLoadVisitor::loadPaintDeviceFrame(KisPaintDeviceSP device, const QString &location, DevicePolicy policy) { { const int pixelSize = device->colorSpace()->pixelSize(); KoColor color(Qt::transparent, device->colorSpace()); if (m_store->open(location + ".defaultpixel")) { if (m_store->size() == pixelSize) { m_store->read((char*)color.data(), pixelSize); } m_store->close(); } policy.setDefaultPixel(device, color); } if (m_store->open(location)) { if (!policy.read(device, m_store->device())) { m_warningMessages << i18n("Could not read pixel data: %1.", location); device->disconnect(); m_store->close(); return true; } m_store->close(); } else { m_warningMessages << i18n("Could not load pixel data: %1.", location); return true; } return true; } bool KisKraLoadVisitor::loadProfile(KisPaintDeviceSP device, const QString& location) { if (m_store->hasFile(location)) { m_store->open(location); QByteArray data; data.resize(m_store->size()); dbgFile << "Data to load: " << m_store->size() << " from " << location << " with color space " << device->colorSpace()->id(); int read = m_store->read(data.data(), m_store->size()); dbgFile << "Profile size: " << data.size() << " " << m_store->atEnd() << " " << m_store->device()->bytesAvailable() << " " << read; m_store->close(); - KoHashGenerator *hashGenerator = KoHashGeneratorProvider::instance()->getGenerator("MD5"); - QByteArray hash = hashGenerator->generateHash(data); + QByteArray hash = KoMD5Generator::generateHash(data); if (m_profileCache.contains(hash)) { if (device->setProfile(m_profileCache[hash], 0)) { return true; } } else { // Create a colorspace with the embedded profile const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(device->colorSpace()->colorModelId().id(), device->colorSpace()->colorDepthId().id(), data); m_profileCache[hash] = profile; if (device->setProfile(profile, 0)) { return true; } } } m_warningMessages << i18n("Could not load profile: %1.", location); return true; } bool KisKraLoadVisitor::loadFilterConfiguration(KisNodeFilterInterface *nodeInterface, const QString& location) { KisFilterConfigurationSP kfc = nodeInterface->filter(); if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement e = doc.documentElement(); if (e.tagName() == "filterconfig") { kfc->fromLegacyXML(e); } else { kfc->fromXML(e); } loadDeprecatedFilter(kfc); return true; } } m_warningMessages << i18n("Could not filter configuration %1.", location); return true; } void KisKraLoadVisitor::fixOldFilterConfigurations(KisFilterConfigurationSP kfc) { KisFilterSP filter = KisFilterRegistry::instance()->value(kfc->name()); KIS_SAFE_ASSERT_RECOVER_RETURN(filter); if (!filter->configurationAllowedForMask(kfc)) { filter->fixLoadedFilterConfigurationForMasks(kfc); } KIS_SAFE_ASSERT_RECOVER_NOOP(filter->configurationAllowedForMask(kfc)); } bool KisKraLoadVisitor::loadMetaData(KisNode* node) { KisLayer* layer = qobject_cast(node); if (!layer) return true; KisMetaData::IOBackend* backend = KisMetaData::IOBackendRegistry::instance()->get("xmp"); if (!backend || !backend->supportLoading()) { if (backend) dbgFile << "Backend " << backend->id() << " does not support loading."; else dbgFile << "Could not load the XMP backend at all"; return true; } QString location = getLocation(node, QString(".") + backend->id() + DOT_METADATA); dbgFile << "going to load " << backend->id() << ", " << backend->name() << " from " << location; if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); QBuffer buffer(&data); if (!backend->loadFrom(layer->metaData(), &buffer)) { m_warningMessages << i18n("Could not load metadata for layer %1.", layer->name()); } } return true; } bool KisKraLoadVisitor::loadSelection(const QString& location, KisSelectionSP dstSelection) { // by default the selection is expected to be fully transparent { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); KoColor transparent(Qt::transparent, pixelSelection->colorSpace()); pixelSelection->setDefaultPixel(transparent); } // Pixel selection bool result = true; QString pixelSelectionLocation = location + DOT_PIXEL_SELECTION; if (m_store->hasFile(pixelSelectionLocation)) { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); result = loadPaintDevice(pixelSelection, pixelSelectionLocation); if (!result) { m_warningMessages << i18n("Could not load raster selection %1.", location); } pixelSelection->invalidateOutlineCache(); } // Shape selection QString shapeSelectionLocation = location + DOT_SHAPE_SELECTION; if (m_store->hasFile(shapeSelectionLocation + "/content.svg") || m_store->hasFile(shapeSelectionLocation + "/content.xml")) { m_store->pushDirectory(); m_store->enterDirectory(shapeSelectionLocation) ; KisShapeSelection* shapeSelection = new KisShapeSelection(m_shapeController, m_image, dstSelection); dstSelection->setShapeSelection(shapeSelection); result = shapeSelection->loadSelection(m_store); m_store->popDirectory(); if (!result) { m_warningMessages << i18n("Could not load vector selection %1.", location); } } return true; } QString KisKraLoadVisitor::getLocation(KisNode* node, const QString& suffix) { return getLocation(m_layerFilenames[node], suffix); } QString KisKraLoadVisitor::getLocation(const QString &filename, const QString& suffix) { QString location = m_external ? QString() : m_uri; location += m_name + LAYER_PATH + filename + suffix; return location; } void KisKraLoadVisitor::loadNodeKeyframes(KisNode *node) { if (!m_keyframeFilenames.contains(node)) return; node->enableAnimation(); const QString &location = getLocation(m_keyframeFilenames[node]); if (!m_store->open(location)) { m_errorMessages << i18n("Could not load keyframes from %1.", location); return; } QString errorMsg; int errorLine; int errorColumn; QDomDocument dom; bool ok = dom.setContent(m_store->device(), &errorMsg, &errorLine, &errorColumn); m_store->close(); if (!ok) { m_errorMessages << i18n("parsing error in the keyframe file %1 at line %2, column %3\nError message: %4", location, errorLine, errorColumn, i18n(errorMsg.toUtf8())); return; } QDomElement root = dom.firstChildElement(); for (QDomElement child = root.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (child.nodeName().toUpper() == "CHANNEL") { QString id = child.attribute("name"); KisKeyframeChannel *channel = node->getKeyframeChannel(id, true); if (!channel) { m_warningMessages << i18n("unknown keyframe channel type: %1 in %2", id, location); continue; } channel->loadXML(child); } } } void KisKraLoadVisitor::loadDeprecatedFilter(KisFilterConfigurationSP cfg) { if (cfg->getString("legacy") == "left edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "yFall"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "right edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "yGrowth"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "top edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "xGrowth"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "bottom edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "xFall"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } } diff --git a/plugins/impex/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp index 849e95ece1..a51dc15495 100644 --- a/plugins/impex/libkra/kis_kra_loader.cpp +++ b/plugins/impex/libkra/kis_kra_loader.cpp @@ -1,1284 +1,1285 @@ /* 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 #include #include #include "lazybrush/kis_colorize_mask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceServerProvider.h" #include "kis_keyframe_channel.h" #include #include "KisReferenceImagesLayer.h" #include "KisReferenceImage.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" #include "KisMirrorAxisConfig.h" #include /* 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; QVector paletteFilenames; 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; 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); } } KisProofingConfigurationSP proofingConfig = KisImageConfig(true).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, m_d->imageName); } else { image = new KisImage(0, width, height, cs, m_d->imageName); } 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() == GLOBALASSISTANTSCOLOR) { if (e.hasAttribute(SIMPLECOLORDATA)) { QString colorData = e.attribute(SIMPLECOLORDATA); m_d->document->setAssistantsGlobalColor(KisDomUtils::qStringToQColor(colorData)); } } if(e.tagName()== PROOFINGWARNINGCOLOR) { QDomDocument dom; KoXml::asQDomElement(dom, e); QDomElement eq = dom.firstChildElement(); 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() == MIRROR_AXIS) { loadMirrorAxis(e); } else if (e.tagName() == "assistants") { loadAssistantsList(e); } else if (e.tagName() == "audio") { loadAudio(e, image); } } // reading palettes from XML for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { QDomElement e = child.toElement(); if (e.tagName() == PALETTES) { for (QDomElement paletteElement = e.lastChildElement(); !paletteElement.isNull(); paletteElement = paletteElement.previousSiblingElement()) { QString paletteName = paletteElement.attribute("filename"); m_d->paletteFilenames.append(paletteName); } break; } } 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, true); image->waitForDone(); } if (!res) { const QString defaultProfileId = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(image->colorSpace()->id()); profile = KoColorSpaceRegistry::instance()->profileByName(defaultProfileId); Q_ASSERT(profile && profile->valid()); image->assignImageProfile(profile, true); image->waitForDone(); } } } } //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(); KisProofingConfigurationSP proofingConfig = image->proofingConfiguration(); if (!proofingConfig) { proofingConfig = KisImageConfig(true).defaultProofingconfiguration(); } if (proofingProfileRes) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingData); if (proofingProfile->valid()){ 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->document->shapeController(), 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)) { warnKrita << "WARNING: Asl Layer Styles cannot be read (part of resource rewrite)."; // TODO: RESOURCES: needs implementing of creation of the storage and linking it to the database etc. // Question: do we need to use KisAslStorage or the document storage? // see: QSharedPointer storage = KisResourceServerProvider::instance()->storageByName(m_d->document->uniqueID()); // and then: storage->addResource(aslStyle); // or through the server: get the server, add everything directly to the server // but I believe it should go through the KisAslStorage? // tiar // //KisPSDLayerStyleSP collection(new KisPSDLayerStyle("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!"; // } } 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); } void KisKraLoader::loadPalettes(KoStore *store, KisDocument *doc) { + qDebug() << ">>>> loadPalettes" << m_d->paletteFilenames; QList list; Q_FOREACH (const QString &filename, m_d->paletteFilenames) { + qDebug() << "loading palettes" << filename; KoColorSetSP newPalette(new KoColorSet(filename)); store->open(m_d->imageName + PALETTE_PATH + filename); QByteArray data = store->read(store->size()); newPalette->fromByteArray(data, KisGlobalResourcesInterface::instance()); - newPalette->setIsGlobal(false); newPalette->setIsEditable(true); store->close(); list.append(newPalette); } doc->setPaletteList(list); } 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; } QString KisKraLoader::imageName() const { return m_d->imageName; } void KisKraLoader::loadAssistants(KoStore *store, const QString &uri, bool external) { QString file_path; QString location; QMap handleMap; KisPaintingAssistant* assistant = 0; const QColor globalColor = m_d->document->assistantsGlobalColor(); 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); assistant->setAssistantGlobalColorCache(globalColor); //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); if (node) { image->nextLayerName(); // Make sure the nameserver is current with the number of nodes. image->addNode(node, parent); if (node->inherits("KisLayer") && KoXml::childNodesCount(child) > 0) { loadNodes(child.toElement(), image, node); } } } } } } return parent; } KisNodeSP KisKraLoader::loadNode(const KoXmlElement& element, KisImageSP image) { // 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->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->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); else if (nodeType == TRANSFORM_MASK) node = loadTransformMask(element); else if (nodeType == TRANSPARENCY_MASK) node = loadTransparencyMask(element); else if (nodeType == SELECTION_MASK) node = loadSelectionMask(image, element); else if (nodeType == COLORIZE_MASK) node = loadColorizeMask(image, element, colorSpace); else if (nodeType == FILE_LAYER) node = loadFileLayer(element, image, name, opacity); else if (nodeType == REFERENCE_IMAGES_LAYER) node = loadReferenceImagesLayer(element, image); else { 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->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); } } const bool timelineEnabled = element.attribute(VISIBLE_IN_TIMELINE, "0") == "0" ? false : true; node->setUseInTimeline(timelineEnabled); 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); } 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); 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 = QDir(basePath).filePath(QDir::cleanPath(filename)); 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::supportedMimeTypes(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; QString legacy = filtername; if ((filtername = element.attribute(FILTER_NAME)).isNull()) { // XXX: Invalid adjustmentlayer! We should warn about it! warnFile << "No filter in adjustment layer"; return 0; } //get deprecated filters. if (filtername=="brightnesscontrast") { legacy = filtername; filtername = "perchannel"; } if (filtername=="left edge detections" || filtername=="right edge detections" || filtername=="top edge detections" || filtername=="bottom edge detections") { legacy = filtername; filtername = "edge detection"; } 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->factoryConfiguration(); kfc->setProperty("legacy", legacy); if (legacy=="brightnesscontrast") { kfc->setProperty("colorModel", cs->colorModelId().id()); } // 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; KoShapeControllerBase * 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->factoryConfiguration(); // 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); KisNodeUuidInfo info; if (! (element.attribute(CLONE_FROM_UUID)).isNull()) { info = KisNodeUuidInfo(QUuid(element.attribute(CLONE_FROM_UUID))); } else { if ((element.attribute(CLONE_FROM)).isNull()) { return 0; } else { info = KisNodeUuidInfo(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) { 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->factoryConfiguration(); // 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) { Q_UNUSED(element); 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) { Q_UNUSED(element); KisTransparencyMask* mask = new KisTransparencyMask(); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadSelectionMask(KisImageSP image, const KoXmlElement& element) { 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, const KoColorSpace *colorSpace) { KisColorizeMaskSP mask = new KisColorizeMask(); const bool editKeystrokes = element.attribute(COLORIZE_EDIT_KEYSTROKES, "1") == "0" ? false : true; const 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); const bool useEdgeDetection = KisDomUtils::toInt(element.attribute(COLORIZE_USE_EDGE_DETECTION, "0")); const qreal edgeDetectionSize = KisDomUtils::toDouble(element.attribute(COLORIZE_EDGE_DETECTION_SIZE, "4")); const qreal radius = KisDomUtils::toDouble(element.attribute(COLORIZE_FUZZY_RADIUS, "0")); const int cleanUp = KisDomUtils::toInt(element.attribute(COLORIZE_CLEANUP, "0")); const bool limitToDevice = KisDomUtils::toInt(element.attribute(COLORIZE_LIMIT_TO_DEVICE, "0")); mask->setUseEdgeDetection(useEdgeDetection); mask->setEdgeDetectionSize(edgeDetectionSize); mask->setFuzzyRadius(radius); mask->setCleanUpAmount(qreal(cleanUp) / 100.0); mask->setLimitToDeviceBounds(limitToDevice); 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::loadMirrorAxis(const KoXmlElement &elem) { QDomDocument dom; KoXml::asQDomElement(dom, elem); QDomElement domElement = dom.firstChildElement(); KisMirrorAxisConfig mirrorAxis; mirrorAxis.loadFromXml(domElement); m_d->document->setMirrorAxisConfig(mirrorAxis); } 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); } } KisNodeSP KisKraLoader::loadReferenceImagesLayer(const KoXmlElement &elem, KisImageSP image) { KisSharedPtr layer = new KisReferenceImagesLayer(m_d->document->shapeController(), image); m_d->document->setReferenceImagesLayer(layer, false); for (QDomElement child = elem.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (child.nodeName().toLower() == "referenceimage") { auto* reference = KisReferenceImage::fromXml(child); layer->addShape(reference); } } return layer; } diff --git a/plugins/impex/libkra/kis_kra_saver.cpp b/plugins/impex/libkra/kis_kra_saver.cpp index 2a5c3866cb..94709b2fe5 100644 --- a/plugins/impex/libkra/kis_kra_saver.cpp +++ b/plugins/impex/libkra/kis_kra_saver.cpp @@ -1,531 +1,530 @@ /* This file is part of the KDE project * Copyright 2008 (C) Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_kra_saver.h" #include "kis_kra_tags.h" #include "kis_kra_save_visitor.h" #include "kis_kra_savexml_visitor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" #include "kis_keyframe_channel.h" #include #include "KisDocument.h" #include #include "kis_dom_utils.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "KisProofingConfiguration.h" #include #include #include using namespace KRA; struct KisKraSaver::Private { public: KisDocument* doc; QMap nodeFileNames; QMap keyframeFilenames; QString imageName; QString filename; QStringList errorMessages; }; KisKraSaver::KisKraSaver(KisDocument* document, const QString &filename) : m_d(new Private) { m_d->doc = document; m_d->filename = filename; m_d->imageName = m_d->doc->documentInfo()->aboutInfo("title"); if (m_d->imageName.isEmpty()) { m_d->imageName = i18n("Unnamed"); } } KisKraSaver::~KisKraSaver() { delete m_d; } QDomElement KisKraSaver::saveXML(QDomDocument& doc, KisImageSP image) { QDomElement imageElement = doc.createElement("IMAGE"); Q_ASSERT(image); imageElement.setAttribute(NAME, m_d->imageName); imageElement.setAttribute(MIME, NATIVE_MIMETYPE); imageElement.setAttribute(WIDTH, KisDomUtils::toString(image->width())); imageElement.setAttribute(HEIGHT, KisDomUtils::toString(image->height())); imageElement.setAttribute(COLORSPACE_NAME, image->colorSpace()->id()); imageElement.setAttribute(DESCRIPTION, m_d->doc->documentInfo()->aboutInfo("comment")); // XXX: Save profile as blob inside the image, instead of the product name. if (image->profile() && image->profile()-> valid()) { imageElement.setAttribute(PROFILE, image->profile()->name()); } imageElement.setAttribute(X_RESOLUTION, KisDomUtils::toString(image->xRes()*72.0)); imageElement.setAttribute(Y_RESOLUTION, KisDomUtils::toString(image->yRes()*72.0)); //now the proofing options: if (image->proofingConfiguration()) { imageElement.setAttribute(PROOFINGPROFILENAME, KisDomUtils::toString(image->proofingConfiguration()->proofingProfile)); imageElement.setAttribute(PROOFINGMODEL, KisDomUtils::toString(image->proofingConfiguration()->proofingModel)); imageElement.setAttribute(PROOFINGDEPTH, KisDomUtils::toString(image->proofingConfiguration()->proofingDepth)); imageElement.setAttribute(PROOFINGINTENT, KisDomUtils::toString(image->proofingConfiguration()->intent)); imageElement.setAttribute(PROOFINGADAPTATIONSTATE, KisDomUtils::toString(image->proofingConfiguration()->adaptationState)); } quint32 count = 1; // We don't save the root layer, but it does count KisSaveXmlVisitor visitor(doc, imageElement, count, m_d->doc->url().toLocalFile(), true); visitor.setSelectedNodes({m_d->doc->preActivatedNode()}); image->rootLayer()->accept(visitor); m_d->errorMessages.append(visitor.errorMessages()); m_d->nodeFileNames = visitor.nodeFileNames(); m_d->keyframeFilenames = visitor.keyframeFileNames(); saveBackgroundColor(doc, imageElement, image); saveAssistantsGlobalColor(doc, imageElement); saveWarningColor(doc, imageElement, image); saveCompositions(doc, imageElement, image); saveAssistantsList(doc, imageElement); saveGrid(doc, imageElement); saveGuides(doc, imageElement); saveMirrorAxis(doc, imageElement); saveAudio(doc, imageElement); savePalettesToXML(doc, imageElement); QDomElement animationElement = doc.createElement("animation"); KisDomUtils::saveValue(&animationElement, "framerate", image->animationInterface()->framerate()); KisDomUtils::saveValue(&animationElement, "range", image->animationInterface()->fullClipRange()); KisDomUtils::saveValue(&animationElement, "currentTime", image->animationInterface()->currentUITime()); imageElement.appendChild(animationElement); return imageElement; } bool KisKraSaver::savePalettes(KoStore *store, KisImageSP image, const QString &uri) { Q_UNUSED(image); Q_UNUSED(uri); + qDebug() << "saving palettes to document" << m_d->doc->paletteList().size(); + bool res = false; if (m_d->doc->paletteList().size() == 0) { return true; } for (const KoColorSetSP palette : m_d->doc->paletteList()) { - if (!palette->isGlobal()) { - if (!store->open(m_d->imageName + PALETTE_PATH + palette->filename())) { - m_d->errorMessages << i18n("could not save palettes"); - return false; - } - QByteArray ba = palette->toByteArray(); - if (!ba.isEmpty()) { - store->write(ba); - } else { - qWarning() << "Cannot save the palette to a byte array:" << palette->name(); - } - store->close(); - res = true; + qDebug() << "saving palette..." << palette->storageLocation() << palette->filename(); + if (!store->open(m_d->imageName + PALETTE_PATH + palette->filename())) { + m_d->errorMessages << i18n("could not save palettes"); + return false; + } + QByteArray ba = palette->toByteArray(); + if (!ba.isEmpty()) { + store->write(ba); + } else { + qWarning() << "Cannot save the palette to a byte array:" << palette->name(); } + store->close(); + res = true; } return res; } void KisKraSaver::savePalettesToXML(QDomDocument &doc, QDomElement &element) { QDomElement ePalette = doc.createElement(PALETTES); for (const KoColorSetSP palette : m_d->doc->paletteList()) { - if (!palette->isGlobal()) { - QDomElement eFile = doc.createElement("palette"); - eFile.setAttribute("filename", palette->filename()); - ePalette.appendChild(eFile); - } + QDomElement eFile = doc.createElement("palette"); + eFile.setAttribute("filename", palette->filename()); + ePalette.appendChild(eFile); } element.appendChild(ePalette); } bool KisKraSaver::saveKeyframes(KoStore *store, const QString &uri, bool external) { QMap::iterator it; for (it = m_d->keyframeFilenames.begin(); it != m_d->keyframeFilenames.end(); it++) { const KisNode *node = it.key(); QString filename = it.value(); QString location = (external ? QString() : uri) + m_d->imageName + LAYER_PATH + filename; if (!saveNodeKeyframes(store, location, node)) { return false; } } return true; } bool KisKraSaver::saveNodeKeyframes(KoStore *store, QString location, const KisNode *node) { QDomDocument doc = KisDocument::createDomDocument("krita-keyframes", "keyframes", "1.0"); QDomElement root = doc.documentElement(); KisKeyframeChannel *channel; Q_FOREACH (channel, node->keyframeChannels()) { QDomElement element = channel->toXML(doc, m_d->nodeFileNames[node]); root.appendChild(element); } if (store->open(location)) { QByteArray xml = doc.toByteArray(); store->write(xml); store->close(); } else { m_d->errorMessages << i18n("could not save keyframes"); return false; } return true; } bool KisKraSaver::saveBinaryData(KoStore* store, KisImageSP image, const QString &uri, bool external, bool autosave) { QString location; // Save the layers data KisKraSaveVisitor visitor(store, m_d->imageName, m_d->nodeFileNames); if (external) visitor.setExternalUri(uri); image->rootLayer()->accept(visitor); m_d->errorMessages.append(visitor.errorMessages()); if (!m_d->errorMessages.isEmpty()) { return false; } // saving annotations // XXX this only saves EXIF and ICC info. This would probably need // a redesign of the dtd of the krita file to do this more generally correct // e.g. have tags or so. KisAnnotationSP annotation = image->annotation("exif"); if (annotation) { location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } if (image->profile()) { const KoColorProfile *profile = image->profile(); KisAnnotationSP annotation; if (profile) { QByteArray profileRawData = profile->rawData(); if (!profileRawData.isEmpty()) { if (profile->type() == "icc") { annotation = new KisAnnotation(ICC, profile->name(), profile->rawData()); } else { annotation = new KisAnnotation(PROFILE, profile->name(), profile->rawData()); } } } if (annotation) { location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } } //This'll embed the profile used for proofing into the kra file. if (image->proofingConfiguration()) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->profileByName(image->proofingConfiguration()->proofingProfile); if (proofingProfile && proofingProfile->valid()) { QByteArray proofingProfileRaw = proofingProfile->rawData(); if (!proofingProfileRaw.isEmpty()) { annotation = new KisAnnotation(ICCPROOFINGPROFILE, proofingProfile->name(), proofingProfile->rawData()); } } if (annotation) { location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } } { warnKrita << "WARNING: Asl Layer Styles cannot be written (part of resource rewrite)."; // TODO: RESOURCES: needs implementation /* KisPSDLayerStyleCollectionResource collection("not-nexists.asl"); KIS_ASSERT_RECOVER_NOOP(!collection.valid()); collection.collectAllLayerStyles(image->root()); if (collection.valid()) { location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->open(location)) { QBuffer aslBuffer; aslBuffer.open(QIODevice::WriteOnly); collection.saveToDevice(&aslBuffer); aslBuffer.close(); store->write(aslBuffer.buffer()); store->close(); } } */ } if (!autosave) { KisPaintDeviceSP dev = image->projection(); KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store); } saveAssistants(store, uri,external); return true; } QStringList KisKraSaver::errorMessages() const { return m_d->errorMessages; } void KisKraSaver::saveBackgroundColor(QDomDocument& doc, QDomElement& element, KisImageSP image) { QDomElement e = doc.createElement(CANVASPROJECTIONCOLOR); KoColor color = image->defaultProjectionColor(); QByteArray colorData = QByteArray::fromRawData((const char*)color.data(), color.colorSpace()->pixelSize()); e.setAttribute(COLORBYTEDATA, QString(colorData.toBase64())); element.appendChild(e); } void KisKraSaver::saveAssistantsGlobalColor(QDomDocument& doc, QDomElement& element) { QDomElement e = doc.createElement(GLOBALASSISTANTSCOLOR); QString colorString = KisDomUtils::qColorToQString(m_d->doc->assistantsGlobalColor()); e.setAttribute(SIMPLECOLORDATA, QString(colorString)); element.appendChild(e); } void KisKraSaver::saveWarningColor(QDomDocument& doc, QDomElement& element, KisImageSP image) { if (image->proofingConfiguration()) { QDomElement e = doc.createElement(PROOFINGWARNINGCOLOR); KoColor color = image->proofingConfiguration()->warningColor; color.toXML(doc, e); element.appendChild(e); } } void KisKraSaver::saveCompositions(QDomDocument& doc, QDomElement& element, KisImageSP image) { if (!image->compositions().isEmpty()) { QDomElement e = doc.createElement("compositions"); Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) { composition->save(doc, e); } element.appendChild(e); } } bool KisKraSaver::saveAssistants(KoStore* store, QString uri, bool external) { QString location; QMap assistantcounters; QByteArray data; QList assistants = m_d->doc->assistants(); QMap handlemap; if (!assistants.isEmpty()) { Q_FOREACH (KisPaintingAssistantSP assist, assistants){ if (!assistantcounters.contains(assist->id())){ assistantcounters.insert(assist->id(),0); } location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; location += QString(assist->id()+"%1.assistant").arg(assistantcounters[assist->id()]); data = assist->saveXml(handlemap); store->open(location); store->write(data); store->close(); assistantcounters[assist->id()]++; } } return true; } bool KisKraSaver::saveAssistantsList(QDomDocument& doc, QDomElement& element) { int count_ellipse = 0, count_perspective = 0, count_ruler = 0, count_vanishingpoint = 0,count_infiniteruler = 0, count_parallelruler = 0, count_concentricellipse = 0, count_fisheyepoint = 0, count_spline = 0; QList assistants = m_d->doc->assistants(); if (!assistants.isEmpty()) { QDomElement assistantsElement = doc.createElement("assistants"); Q_FOREACH (KisPaintingAssistantSP assist, assistants){ if (assist->id() == "ellipse"){ assist->saveXmlList(doc, assistantsElement, count_ellipse); count_ellipse++; } else if (assist->id() == "spline"){ assist->saveXmlList(doc, assistantsElement, count_spline); count_spline++; } else if (assist->id() == "perspective"){ assist->saveXmlList(doc, assistantsElement, count_perspective); count_perspective++; } else if (assist->id() == "vanishing point"){ assist->saveXmlList(doc, assistantsElement, count_vanishingpoint); count_vanishingpoint++; } else if (assist->id() == "infinite ruler"){ assist->saveXmlList(doc, assistantsElement, count_infiniteruler); count_infiniteruler++; } else if (assist->id() == "parallel ruler"){ assist->saveXmlList(doc, assistantsElement, count_parallelruler); count_parallelruler++; } else if (assist->id() == "concentric ellipse"){ assist->saveXmlList(doc, assistantsElement, count_concentricellipse); count_concentricellipse++; } else if (assist->id() == "fisheye-point"){ assist->saveXmlList(doc, assistantsElement, count_fisheyepoint); count_fisheyepoint++; } else if (assist->id() == "ruler"){ assist->saveXmlList(doc, assistantsElement, count_ruler); count_ruler++; } } element.appendChild(assistantsElement); } return true; } bool KisKraSaver::saveGrid(QDomDocument& doc, QDomElement& element) { KisGridConfig config = m_d->doc->gridConfig(); if (!config.isDefault()) { QDomElement gridElement = config.saveDynamicDataToXml(doc, "grid"); element.appendChild(gridElement); } return true; } bool KisKraSaver::saveGuides(QDomDocument& doc, QDomElement& element) { KisGuidesConfig guides = m_d->doc->guidesConfig(); if (!guides.isDefault()) { QDomElement guidesElement = guides.saveToXml(doc, "guides"); element.appendChild(guidesElement); } return true; } bool KisKraSaver::saveMirrorAxis(QDomDocument &doc, QDomElement &element) { KisMirrorAxisConfig mirrorAxisConfig = m_d->doc->mirrorAxisConfig(); if (!mirrorAxisConfig.isDefault()) { QDomElement mirrorAxisElement = mirrorAxisConfig.saveToXml(doc, MIRROR_AXIS); element.appendChild(mirrorAxisElement); } return true; } bool KisKraSaver::saveAudio(QDomDocument& doc, QDomElement& element) { const KisImageAnimationInterface *interface = m_d->doc->image()->animationInterface(); QString fileName = interface->audioChannelFileName(); if (fileName.isEmpty()) return true; if (!QFileInfo::exists(fileName)) { m_d->errorMessages << i18n("Audio channel file %1 doesn't exist!", fileName); return false; } const QDir documentDir = QFileInfo(m_d->filename).absoluteDir(); KIS_ASSERT_RECOVER_RETURN_VALUE(documentDir.exists(), false); fileName = documentDir.relativeFilePath(fileName); fileName = QDir::fromNativeSeparators(fileName); KIS_ASSERT_RECOVER_RETURN_VALUE(!fileName.isEmpty(), false); QDomElement audioElement = doc.createElement("audio"); KisDomUtils::saveValue(&audioElement, "masterChannelPath", fileName); KisDomUtils::saveValue(&audioElement, "audioMuted", interface->isAudioMuted()); KisDomUtils::saveValue(&audioElement, "audioVolume", interface->audioVolume()); element.appendChild(audioElement); return true; } diff --git a/plugins/paintops/libpaintop/kis_brush_chooser.cpp b/plugins/paintops/libpaintop/kis_brush_chooser.cpp index f7b455f051..e6ec79984a 100644 --- a/plugins/paintops/libpaintop/kis_brush_chooser.cpp +++ b/plugins/paintops/libpaintop/kis_brush_chooser.cpp @@ -1,418 +1,418 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2009 Sven Langkamp * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Srikanth Tiyyagura * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisBrushServerProvider.h" #include "widgets/kis_slider_spin_box.h" #include "widgets/kis_multipliers_double_slider_spinbox.h" #include "kis_spacing_selection_widget.h" #include "kis_signals_blocker.h" #include "kis_imagepipe_brush.h" #include "kis_custom_brush_widget.h" #include "kis_clipboard_brush_widget.h" #include #include "kis_global.h" #include "kis_gbr_brush.h" #include "kis_debug.h" #include "kis_image.h" #include /// The resource item delegate for rendering the resource preview class KisBrushDelegate : public QAbstractItemDelegate { public: KisBrushDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) {} ~KisBrushDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } }; void KisBrushDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { if (! index.isValid()) return; - QImage thumbnail = index.data(Qt::UserRole + KisResourceModel::Image).value(); + QImage thumbnail = index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); QRect itemRect = option.rect; if (thumbnail.height() > itemRect.height() || thumbnail.width() > itemRect.width()) { thumbnail = thumbnail.scaled(itemRect.size() , Qt::KeepAspectRatio, Qt::SmoothTransformation); } painter->save(); int dx = (itemRect.width() - thumbnail.width()) / 2; int dy = (itemRect.height() - thumbnail.height()) / 2; painter->drawImage(itemRect.x() + dx, itemRect.y() + dy, thumbnail); if (option.state & QStyle::State_Selected) { painter->setPen(QPen(option.palette.highlight(), 2.0)); painter->drawRect(option.rect); painter->setCompositionMode(QPainter::CompositionMode_HardLight); painter->setOpacity(0.65); painter->fillRect(option.rect, option.palette.highlight()); } painter->restore(); } KisPredefinedBrushChooser::KisPredefinedBrushChooser(QWidget *parent, const char *name) : QWidget(parent), m_stampBrushWidget(0), m_clipboardBrushWidget(0) { setObjectName(name); setupUi(this); brushSizeSpinBox->setRange(0, KSharedConfig::openConfig()->group("").readEntry("maximumBrushSize", 1000), 2); brushSizeSpinBox->setValue(5); brushSizeSpinBox->setExponentRatio(3.0); brushSizeSpinBox->setSuffix(i18n(" px")); brushSizeSpinBox->setExponentRatio(3.0); QObject::connect(brushSizeSpinBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemSize(qreal))); brushRotationSpinBox->setRange(0, 360, 0); brushRotationSpinBox->setValue(0); brushRotationSpinBox->setSuffix(QChar(Qt::Key_degree)); QObject::connect(brushRotationSpinBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemRotation(qreal))); brushSpacingSelectionWidget->setSpacing(true, 1.0); connect(brushSpacingSelectionWidget, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged())); QObject::connect(useColorAsMaskCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetItemUseColorAsMask(bool))); m_itemChooser = new KisResourceItemChooser(ResourceType::Brushes, false, this); m_itemChooser->setObjectName("brush_selector"); m_itemChooser->showTaggingBar(true); m_itemChooser->setRowHeight(30); m_itemChooser->setItemDelegate(new KisBrushDelegate(this)); m_itemChooser->setCurrentItem(0); m_itemChooser->setSynced(true); m_itemChooser->setMinimumWidth(100); m_itemChooser->setMinimumHeight(150); m_itemChooser->showButtons(false); // turn the import and delete buttons since we want control over them addPresetButton->setIcon(KisIconUtils::loadIcon("list-add")); deleteBrushTipButton->setIcon(KisIconUtils::loadIcon("trash-empty")); connect(addPresetButton, SIGNAL(clicked(bool)), this, SLOT(slotImportNewBrushResource())); connect(deleteBrushTipButton, SIGNAL(clicked(bool)), this, SLOT(slotDeleteBrushResource())); presetsLayout->addWidget(m_itemChooser); connect(m_itemChooser, SIGNAL(resourceSelected(KoResourceSP )), this, SLOT(updateBrushTip(KoResourceSP ))); stampButton->setIcon(KisIconUtils::loadIcon("list-add")); stampButton->setToolTip(i18n("Creates a brush tip from the current image selection." "\n If no selection is present the whole image will be used.")); clipboardButton->setIcon(KisIconUtils::loadIcon("list-add")); clipboardButton->setToolTip(i18n("Creates a brush tip from the image in the clipboard.")); connect(stampButton, SIGNAL(clicked()), this, SLOT(slotOpenStampBrush())); connect(clipboardButton, SIGNAL(clicked()), SLOT(slotOpenClipboardBrush())); QGridLayout *spacingLayout = new QGridLayout(); spacingLayout->setObjectName("spacing grid layout"); resetBrushButton->setToolTip(i18n("Reloads Spacing from file\nSets Scale to 1.0\nSets Rotation to 0.0")); connect(resetBrushButton, SIGNAL(clicked()), SLOT(slotResetBrush())); updateBrushTip(m_itemChooser->currentResource()); } KisPredefinedBrushChooser::~KisPredefinedBrushChooser() { } void KisPredefinedBrushChooser::setBrush(KisBrushSP brush) { /** * Warning: since the brushes are always cloned after loading from XML or * fetching from the server, we cannot just ask for that brush explicitly. * Instead, we should search for the brush with the same filename and/or name * and load it. Please take it into account that after selecting the brush * explicitly in the chooser, m_itemChooser->currentResource() might be * **not** the same as the value in m_brush. * * Ideally, if the resource is not found on the server, we should add it, but * it might lead to a set of weird consequences. So for now we just * select nothing. */ KoResourceServer* server = KisBrushServerProvider::instance()->brushServer(); KoResourceSP resource = server->resourceByFilename(brush->filename()); if (!resource) { resource = server->resourceByName(brush->name()); } if (!resource) { resource = brush; } m_itemChooser->setCurrentResource(resource); updateBrushTip(brush, true); } void KisPredefinedBrushChooser::slotResetBrush() { /** * The slot also resets the brush on the server * * TODO: technically, after we refactored all the brushes to be forked, * we can just re-update the brush from the server without reloading. * But it needs testing. */ KisBrushSP brush = m_itemChooser->currentResource().dynamicCast(); if (brush) { brush->load(KisGlobalResourcesInterface::instance()); brush->setScale(1.0); brush->setAngle(0.0); updateBrushTip(brush); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSetItemSize(qreal sizeValue) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { int brushWidth = m_brush->width(); m_brush->setScale(sizeValue / qreal(brushWidth)); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSetItemRotation(qreal rotationValue) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { m_brush->setAngle(rotationValue / 180.0 * M_PI); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSpacingChanged() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { m_brush->setSpacing(brushSpacingSelectionWidget->spacing()); m_brush->setAutoSpacing(brushSpacingSelectionWidget->autoSpacingActive(), brushSpacingSelectionWidget->autoSpacingCoeff()); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSetItemUseColorAsMask(bool useColorAsMask) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); KisGbrBrush *brush = dynamic_cast(m_brush.data()); if (brush) { brush->setUseColorAsMask(useColorAsMask); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotOpenStampBrush() { if(!m_stampBrushWidget) { m_stampBrushWidget = new KisCustomBrushWidget(this, i18n("Stamp"), m_image); m_stampBrushWidget->setModal(false); connect(m_stampBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResourceSP )), SLOT(slotNewPredefinedBrush(KoResourceSP ))); } else { m_stampBrushWidget->setImage(m_image); } QDialog::DialogCode result = (QDialog::DialogCode)m_stampBrushWidget->exec(); if(result) { updateBrushTip(m_itemChooser->currentResource()); } } void KisPredefinedBrushChooser::slotOpenClipboardBrush() { if(!m_clipboardBrushWidget) { m_clipboardBrushWidget = new KisClipboardBrushWidget(this, i18n("Clipboard"), m_image); m_clipboardBrushWidget->setModal(true); connect(m_clipboardBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResourceSP )), SLOT(slotNewPredefinedBrush(KoResourceSP ))); } QDialog::DialogCode result = (QDialog::DialogCode)m_clipboardBrushWidget->exec(); if(result) { updateBrushTip(m_itemChooser->currentResource()); } } void KisPredefinedBrushChooser::updateBrushTip(KoResourceSP resource, bool isChangingBrushPresets) { QString animatedBrushTipSelectionMode; // incremental, random, etc { KisBrushSP brush = resource.dynamicCast(); m_brush = brush ? brush->clone().dynamicCast() : 0; } if (m_brush) { brushTipNameLabel->setText(i18n(m_brush->name().toUtf8().data())); QString brushTypeString = ""; if (m_brush->brushType() == INVALID) { brushTypeString = i18n("Invalid"); } else if (m_brush->brushType() == MASK) { brushTypeString = i18n("Mask"); } else if (m_brush->brushType() == IMAGE) { brushTypeString = i18n("GBR"); } else if (m_brush->brushType() == PIPE_MASK ) { brushTypeString = i18n("Animated Mask"); // GIH brush // cast to GIH brush and grab parasite name //m_brush KisImagePipeBrushSP pipeBrush = resource.dynamicCast(); animatedBrushTipSelectionMode = pipeBrush->parasiteSelection(); } else if (m_brush->brushType() == PIPE_IMAGE ) { brushTypeString = i18n("Animated Image"); } QString brushDetailsText = QString("%1 (%2 x %3) %4") .arg(brushTypeString) .arg(m_brush->width()) .arg(m_brush->height()) .arg(animatedBrushTipSelectionMode); brushDetailsLabel->setText(brushDetailsText); // keep the current preset's tip settings if we are preserving it // this will set the brush's model data to keep what it currently has for size, spacing, etc. if (preserveBrushPresetSettings->isChecked() && !isChangingBrushPresets) { m_brush->setAutoSpacing(brushSpacingSelectionWidget->autoSpacingActive(), brushSpacingSelectionWidget->autoSpacingCoeff()); m_brush->setAngle(brushRotationSpinBox->value() * M_PI / 180); m_brush->setSpacing(brushSpacingSelectionWidget->spacing()); m_brush->setUserEffectiveSize(brushSizeSpinBox->value()); } brushSpacingSelectionWidget->setSpacing(m_brush->autoSpacingActive(), m_brush->autoSpacingActive() ? m_brush->autoSpacingCoeff() : m_brush->spacing()); brushRotationSpinBox->setValue(m_brush->angle() * 180 / M_PI); brushSizeSpinBox->setValue(m_brush->width() * m_brush->scale()); // useColorAsMask support is only in gimp brush so far bool prevColorAsMaskState = useColorAsMaskCheckbox->isChecked(); KisGbrBrush *gimpBrush = dynamic_cast(m_brush.data()); if (gimpBrush) { useColorAsMaskCheckbox->setChecked(gimpBrush->useColorAsMask() || prevColorAsMaskState); gimpBrush->setUseColorAsMask(prevColorAsMaskState); } useColorAsMaskCheckbox->setEnabled(m_brush->hasColor() && gimpBrush); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotNewPredefinedBrush(KoResourceSP resource) { m_itemChooser->setCurrentResource(resource); updateBrushTip(resource); } void KisPredefinedBrushChooser::setBrushSize(qreal xPixels, qreal yPixels) { Q_UNUSED(yPixels); qreal oldWidth = m_brush->width() * m_brush->scale(); qreal newWidth = oldWidth + xPixels; newWidth = qMax(newWidth, qreal(0.1)); brushSizeSpinBox->setValue(newWidth); } void KisPredefinedBrushChooser::setImage(KisImageWSP image) { m_image = image; } void KisPredefinedBrushChooser::slotImportNewBrushResource() { m_itemChooser->slotButtonClicked(KisResourceItemChooser::Button_Import); } void KisPredefinedBrushChooser::slotDeleteBrushResource() { m_itemChooser->slotButtonClicked(KisResourceItemChooser::Button_Remove); } #include "moc_kis_brush_chooser.cpp" diff --git a/plugins/python/mixer_slider_docker/kritapykrita_mixer_slider_docker.desktop b/plugins/python/mixer_slider_docker/kritapykrita_mixer_slider_docker.desktop index 6c3cb0bd23..446a05ad34 100644 --- a/plugins/python/mixer_slider_docker/kritapykrita_mixer_slider_docker.desktop +++ b/plugins/python/mixer_slider_docker/kritapykrita_mixer_slider_docker.desktop @@ -1,30 +1,32 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=mixer_slider_docker X-Python-2-Compatible=false X-Krita-Manual=Manual.html Name=Mixer Slider docker Name[ca]=Acoblador Control lliscant del mesclador Name[ca@valencia]=Acoblador Control lliscant del mesclador Name[es]=Panel del deslizador del mezclador Name[et]=Mikseri liuguri dokk Name[it]=Area di aggancio cursore di miscelazione +Name[ko]=믹서 슬라이더 도커 Name[nl]=Vastzetter van schuifregelaar van mixer Name[pt]=Área da Barra de Mistura Name[sv]=Dockningsfönster för blandningsreglage Name[uk]=Бічна панель повзунка мікшера Name[x-test]=xxMixer Slider dockerxx Name[zh_TW]=混色滑動條工具面板 Comment=A color slider. Comment[ca]=Un control lliscant per al color. Comment[ca@valencia]=Un control lliscant per al color. Comment[es]=Deslizador de color. Comment[et]=Värviliugur. Comment[it]=Cursore del colore. +Comment[ko]=컬러 슬라이더입니다. Comment[nl]=Een schuifregelaar voor kleur Comment[pt]=Uma barra de cores. Comment[sv]=Ett färgreglage. Comment[uk]=Повзунок кольору. Comment[x-test]=xxA color slider.xx Comment[zh_TW]=色彩滑動條。 diff --git a/plugins/tools/basictools/kis_tool_colorpicker.cc b/plugins/tools/basictools/kis_tool_colorpicker.cc index 135f78eb95..eb06bcdd9b 100644 --- a/plugins/tools/basictools/kis_tool_colorpicker.cc +++ b/plugins/tools/basictools/kis_tool_colorpicker.cc @@ -1,371 +1,365 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2018 Emmet & Eoin O'Neill * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_colorpicker.h" #include #include #include "kis_cursor.h" #include "KisDocument.h" #include "kis_canvas2.h" #include "KisReferenceImagesLayer.h" #include "KoCanvasBase.h" #include "kis_random_accessor_ng.h" #include "KoResourceServerProvider.h" #include #include "kis_wrapped_rect.h" #include "kis_tool_utils.h" namespace { // GUI ComboBox index constants const int SAMPLE_MERGED = 0; } KisToolColorPicker::KisToolColorPicker(KoCanvasBase *canvas) : KisTool(canvas, KisCursor::pickerCursor()), m_config(new KisToolUtils::ColorPickerConfig) { setObjectName("tool_colorpicker"); m_isActivated = false; m_optionsWidget = 0; m_pickedColor = KoColor(); } KisToolColorPicker::~KisToolColorPicker() { if (m_isActivated) { m_config->save(m_toolActivationSource == KisTool::DefaultActivation); } } void KisToolColorPicker::paint(QPainter &gc, const KoViewConverter &converter) { Q_UNUSED(gc); Q_UNUSED(converter); } void KisToolColorPicker::activate(ToolActivation activation, const QSet &shapes) { m_isActivated = true; m_toolActivationSource = activation; m_config->load(m_toolActivationSource == KisTool::DefaultActivation); updateOptionWidget(); KisTool::activate(activation, shapes); } void KisToolColorPicker::deactivate() { m_config->save(m_toolActivationSource == KisTool::DefaultActivation); m_isActivated = false; KisTool::deactivate(); } bool KisToolColorPicker::pickColor(const QPointF &pos) { // Timer check. if (m_colorPickerDelayTimer.isActive()) { return false; } else { m_colorPickerDelayTimer.setSingleShot(true); m_colorPickerDelayTimer.start(100); } QScopedPointer> imageLocker; m_pickedColor.setOpacity(0.0); // Pick from reference images. if (m_optionsWidget->cmbSources->currentIndex() == SAMPLE_MERGED) { auto *kisCanvas = dynamic_cast(canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(kisCanvas, false); KisSharedPtr referenceImageLayer = kisCanvas->imageView()->document()->referenceImagesLayer(); if (referenceImageLayer && kisCanvas->referenceImagesDecoration()->visible()) { QColor color = referenceImageLayer->getPixel(pos); if (color.isValid()) { m_pickedColor.fromQColor(color); } } } if (m_pickedColor.opacityU8() == OPACITY_TRANSPARENT_U8) { if (!currentImage()->bounds().contains(pos.toPoint()) && !currentImage()->wrapAroundModePermitted()) { return false; } KisPaintDeviceSP dev; if (m_optionsWidget->cmbSources->currentIndex() != SAMPLE_MERGED && currentNode() && currentNode()->colorPickSourceDevice()) { dev = currentNode()->colorPickSourceDevice(); } else { imageLocker.reset(new boost::lock_guard(*currentImage())); dev = currentImage()->projection(); } KoColor previousColor = canvas()->resourceManager()->foregroundColor(); KisToolUtils::pickColor(m_pickedColor, dev, pos.toPoint(), &previousColor, m_config->radius, m_config->blend); } if (m_config->updateColor && m_pickedColor.opacityU8() != OPACITY_TRANSPARENT_U8) { KoColor publicColor = m_pickedColor; publicColor.setOpacity(OPACITY_OPAQUE_U8); // Alpha is unwanted for FG and BG colors. if (m_config->toForegroundColor) { canvas()->resourceManager()->setResource(KoCanvasResourceProvider::ForegroundColor, publicColor); } else { canvas()->resourceManager()->setResource(KoCanvasResourceProvider::BackgroundColor, publicColor); } } return true; } void KisToolColorPicker::beginPrimaryAction(KoPointerEvent *event) { bool sampleMerged = m_optionsWidget->cmbSources->currentIndex() == SAMPLE_MERGED; if (!sampleMerged) { if (!currentNode()) { QMessageBox::information(0, i18nc("@title:window", "Krita"), i18n("Cannot pick a color as no layer is active.")); event->ignore(); return; } if (!currentNode()->visible()) { QMessageBox::information(0, i18nc("@title:window", "Krita"), i18n("Cannot pick a color as the active layer is not visible.")); event->ignore(); return; } } QPoint pos = convertToImagePixelCoordFloored(event); setMode(KisTool::PAINT_MODE); bool picked = pickColor(pos); if (!picked) { // Color picking has to start in the visible part of the layer event->ignore(); return; } displayPickedColor(); } void KisToolColorPicker::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); QPoint pos = convertToImagePixelCoordFloored(event); pickColor(pos); displayPickedColor(); } #include "kis_display_color_converter.h" void KisToolColorPicker::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (m_config->addColorToCurrentPalette) { KisSwatch swatch; swatch.setColor(m_pickedColor); // We don't ask for a name, too intrusive here KoColorSetSP palette = m_palettes.at(m_optionsWidget->cmbPalette->currentIndex()); palette->add(swatch); if (!palette->save()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Cannot write to palette file %1. Maybe it is read-only.", palette->filename())); } } } struct PickedChannel { QString name; QString valueText; }; void KisToolColorPicker::displayPickedColor() { if (m_pickedColor.data() && m_optionsWidget) { QList channels = m_pickedColor.colorSpace()->channels(); m_optionsWidget->listViewChannels->clear(); QVector pickedChannels; for (int i = 0; i < channels.count(); ++i) { pickedChannels.append(PickedChannel()); } for (int i = 0; i < channels.count(); ++i) { PickedChannel pc; pc.name = channels[i]->name(); if (m_config->normaliseValues) { pc.valueText = m_pickedColor.colorSpace()->normalisedChannelValueText(m_pickedColor.data(), i); } else { pc.valueText = m_pickedColor.colorSpace()->channelValueText(m_pickedColor.data(), i); } pickedChannels[channels[i]->displayPosition()] = pc; } Q_FOREACH (const PickedChannel &pc, pickedChannels) { QTreeWidgetItem *item = new QTreeWidgetItem(m_optionsWidget->listViewChannels); item->setText(0, pc.name); item->setText(1, pc.valueText); } KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); KoColor newColor = kritaCanvas->displayColorConverter()->applyDisplayFiltering(m_pickedColor, Float32BitsColorDepthID); QVector values(4); newColor.colorSpace()->normalisedChannelsValue(newColor.data(), values); for (int i = 0; i < values.size(); i++) { QTreeWidgetItem *item = new QTreeWidgetItem(m_optionsWidget->listViewChannels); item->setText(0, QString("DisplayCh%1").arg(i)); item->setText(1, QString::number(values[i])); } } } QWidget* KisToolColorPicker::createOptionWidget() { m_optionsWidget = new ColorPickerOptionsWidget(0); m_optionsWidget->setObjectName(toolId() + " option widget"); m_optionsWidget->listViewChannels->setSortingEnabled(false); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); // Initialize blend KisSliderSpinBox m_optionsWidget->blend->setRange(0,100); m_optionsWidget->blend->setSuffix(i18n("%")); updateOptionWidget(); connect(m_optionsWidget->cbUpdateCurrentColor, SIGNAL(toggled(bool)), SLOT(slotSetUpdateColor(bool))); connect(m_optionsWidget->cbNormaliseValues, SIGNAL(toggled(bool)), SLOT(slotSetNormaliseValues(bool))); connect(m_optionsWidget->cbPalette, SIGNAL(toggled(bool)), SLOT(slotSetAddPalette(bool))); connect(m_optionsWidget->radius, SIGNAL(valueChanged(int)), SLOT(slotChangeRadius(int))); connect(m_optionsWidget->blend, SIGNAL(valueChanged(int)), SLOT(slotChangeBlend(int))); connect(m_optionsWidget->cmbSources, SIGNAL(currentIndexChanged(int)), SLOT(slotSetColorSource(int))); KoResourceServer *srv = KoResourceServerProvider::instance()->paletteServer(); if (!srv) { return m_optionsWidget; } - QList palettes = srv->resources(); - - Q_FOREACH (KoColorSetSP palette, palettes) { - if (palette) { - m_optionsWidget->cmbPalette->addSqueezedItem(palette->name()); - m_palettes.append(palette); - } - } + m_optionsWidget->cmbPalette->setModel(srv->resourceModel()); + m_optionsWidget->cmbPalette->setModelColumn(KisResourceModel::Name); return m_optionsWidget; } void KisToolColorPicker::updateOptionWidget() { if (!m_optionsWidget) return; m_optionsWidget->cbNormaliseValues->setChecked(m_config->normaliseValues); m_optionsWidget->cbUpdateCurrentColor->setChecked(m_config->updateColor); m_optionsWidget->cmbSources->setCurrentIndex(SAMPLE_MERGED + !m_config->sampleMerged); m_optionsWidget->cbPalette->setChecked(m_config->addColorToCurrentPalette); m_optionsWidget->radius->setValue(m_config->radius); m_optionsWidget->blend->setValue(m_config->blend); } void KisToolColorPicker::setToForeground(bool newValue) { m_config->toForegroundColor = newValue; emit toForegroundChanged(); } bool KisToolColorPicker::toForeground() const { return m_config->toForegroundColor; } void KisToolColorPicker::slotSetUpdateColor(bool state) { m_config->updateColor = state; } void KisToolColorPicker::slotSetNormaliseValues(bool state) { m_config->normaliseValues = state; displayPickedColor(); } void KisToolColorPicker::slotSetAddPalette(bool state) { m_config->addColorToCurrentPalette = state; } void KisToolColorPicker::slotChangeRadius(int value) { m_config->radius = value; } void KisToolColorPicker::slotChangeBlend(int value) { m_config->blend = value; } void KisToolColorPicker::slotSetColorSource(int value) { m_config->sampleMerged = value == SAMPLE_MERGED; } void KisToolColorPicker::slotAddPalette(KoResourceSP resource) { KoColorSetSP palette = resource.dynamicCast(); if (palette) { m_optionsWidget->cmbPalette->addSqueezedItem(palette->name()); m_palettes.append(palette); } } 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 90574c1ed7..2cead87cc1 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,379 +1,378 @@ /* * 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() : baseNodeChangedCompressor(500, KisSignalCompressor::FIRST_ACTIVE) { } Ui_KisToolLazyBrushOptionsWidget *ui; KisPaletteModel *colorModel; KisCanvasResourceProvider *provider; KisSignalAutoConnectionsStore providerSignals; KisSignalAutoConnectionsStore maskSignals; KisColorizeMaskSP activeMask; KoColorSetSP colorSet {new KoColorSet(QString())}; int transparentColorIndex; 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->setAllowModification(false); //people proly shouldn't be able to edit the colorentries themselves. m_d->ui->colorView->setCrossedKeyword("transparent"); connect(m_d->ui->chkUseEdgeDetection, SIGNAL(toggled(bool)), SLOT(slotUseEdgeDetectionChanged(bool))); connect(m_d->ui->intEdgeDetectionSize, SIGNAL(valueChanged(int)), SLOT(slotEdgeDetectionSizeChanged(int))); connect(m_d->ui->intRadius, SIGNAL(valueChanged(int)), SLOT(slotRadiusChanged(int))); connect(m_d->ui->intCleanUp, SIGNAL(valueChanged(int)), SLOT(slotCleanUpChanged(int))); connect(m_d->ui->chkLimitToDevice, SIGNAL(toggled(bool)), SLOT(slotLimitToDeviceChanged(bool))); m_d->ui->intEdgeDetectionSize->setRange(0, 100); m_d->ui->intEdgeDetectionSize->setExponentRatio(2.0); m_d->ui->intEdgeDetectionSize->setSuffix(i18n(" px")); m_d->ui->intEdgeDetectionSize->setPrefix(i18n("Edge detection: ")); m_d->ui->intEdgeDetectionSize->setToolTip( i18nc("@info:tooltip", "Activate for images with vast solid areas. " "Set the value to the width of the thinnest " "lines on the image")); m_d->ui->intRadius->setRange(0, 1000); m_d->ui->intRadius->setExponentRatio(3.0); m_d->ui->intRadius->setSuffix(i18n(" px")); m_d->ui->intRadius->setPrefix(i18n("Gap close hint: ")); m_d->ui->intRadius->setToolTip( i18nc("@info:tooltip", "The mask will try to close non-closed contours " "if the gap is smaller than \"Gap close hint\" value")); m_d->ui->intCleanUp->setRange(0, 100); m_d->ui->intCleanUp->setSuffix(i18n(" %")); m_d->ui->intCleanUp->setPrefix(i18n("Clean up: ")); m_d->ui->intCleanUp->setToolTip( i18nc("@info:tooltip", "The mask will try to remove parts of the key strokes " "that are placed outside the closed contours. 0% - no effect, 100% - max effect")); connect(m_d->ui->colorView, SIGNAL(sigIndexSelected(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; - m_d->colorSet->setIsGlobal(false); m_d->colorSet->setIsEditable(true); m_d->colorModel->setPalette(m_d->colorSet); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); m_d->colorModel->addEntry(KisSwatch(KoColor(Qt::red, cs), "color1")); m_d->colorModel->addEntry(KisSwatch(KoColor(Qt::green, cs), "color2")); m_d->colorModel->addEntry(KisSwatch(KoColor(Qt::blue, cs), "color3")); } 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(KoColor)), this, SLOT(slotCurrentFgColorChanged(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; if (!qvariant_cast(index.data(KisPaletteModel::CheckSlotRole))) return; KisSwatch entry = m_d->colorModel->getEntry(index); m_d->provider->setFGColor(entry.color()); int idxInList = m_d->activeMask->keyStrokesColors().colors.indexOf(entry.color()); if (idxInList != -1) { const bool transparentChecked = idxInList == m_d->transparentColorIndex; KisSignalsBlocker b(m_d->ui->btnTransparent); m_d->ui->btnTransparent->setChecked(transparentChecked); } } void KisToolLazyBrushOptionsWidget::slotCurrentFgColorChanged(const KoColor &color) { bool found = false; QModelIndex candidateIdx = m_d->colorModel->indexForClosest(color); if (m_d->colorModel->getEntry(candidateIdx).color() == color) { found = true; } m_d->ui->btnRemove->setEnabled(found); m_d->ui->btnTransparent->setEnabled(found); if (!found) { KisSignalsBlocker b(m_d->ui->btnTransparent); m_d->ui->btnTransparent->setChecked(false); } QModelIndex newIndex = found ? candidateIdx : QModelIndex(); if (!found) { m_d->ui->colorView->selectionModel()->clear(); } if (newIndex.isValid() && newIndex != m_d->ui->colorView->currentIndex()) { m_d->ui->colorView->setCurrentIndex(newIndex); m_d->ui->colorView->update(newIndex); } } void KisToolLazyBrushOptionsWidget::slotColorLabelsChanged() { m_d->colorModel->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->colorModel->addEntry(KisSwatch(colors.colors[i], name)); } } slotCurrentFgColorChanged(m_d->provider->fgColor()); } void KisToolLazyBrushOptionsWidget::slotUpdateNodeProperties() { KisSignalsBlocker b1(m_d->ui->chkAutoUpdates, m_d->ui->btnUpdate, m_d->ui->chkShowKeyStrokes, m_d->ui->chkShowOutput); KisSignalsBlocker b2(m_d->ui->chkUseEdgeDetection, m_d->ui->intEdgeDetectionSize, m_d->ui->intRadius, m_d->ui->intCleanUp, m_d->ui->chkLimitToDevice); // not implemented yet! //m_d->ui->chkAutoUpdates->setEnabled(m_d->activeMask); m_d->ui->chkAutoUpdates->setEnabled(false); m_d->ui->chkAutoUpdates->setVisible(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() && 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); m_d->ui->chkUseEdgeDetection->setEnabled(m_d->activeMask); m_d->ui->chkUseEdgeDetection->setChecked(m_d->activeMask && m_d->activeMask->useEdgeDetection()); m_d->ui->intEdgeDetectionSize->setEnabled(m_d->activeMask && m_d->ui->chkUseEdgeDetection->isChecked()); m_d->ui->intEdgeDetectionSize->setValue(m_d->activeMask ? m_d->activeMask->edgeDetectionSize() : 4.0); m_d->ui->intRadius->setEnabled(m_d->activeMask); m_d->ui->intRadius->setValue(2 * (m_d->activeMask ? m_d->activeMask->fuzzyRadius() : 15)); m_d->ui->intCleanUp->setEnabled(m_d->activeMask); m_d->ui->intCleanUp->setValue(100 * (m_d->activeMask ? m_d->activeMask->cleanUpAmount() : 0.7)); m_d->ui->chkLimitToDevice->setEnabled(m_d->activeMask); m_d->ui->chkLimitToDevice->setChecked(m_d->activeMask && m_d->activeMask->limitToDeviceBounds()); } 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(); KisSwatch activeSwatch = m_d->colorModel->getEntry(index); if (!index.isValid()) return; int activeIndex = -1; KisColorizeMask::KeyStrokeColors colors; int i = 0; Q_FOREACH (const QString &groupName, m_d->colorSet->getGroupNames()) { KisSwatchGroup *group = m_d->colorSet->getGroup(groupName); Q_FOREACH (const KisSwatchGroup::SwatchInfo &info, group->infoList()) { colors.colors << info.swatch.color(); if (activeSwatch == info.swatch) { activeIndex = i; } i++; } } 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 KoColor color = m_d->colorModel->getEntry(index).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) { // not implemented yet! 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()); } void KisToolLazyBrushOptionsWidget::slotUseEdgeDetectionChanged(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask); m_d->activeMask->setUseEdgeDetection(value); m_d->ui->intEdgeDetectionSize->setEnabled(value); } void KisToolLazyBrushOptionsWidget::slotEdgeDetectionSizeChanged(int value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask); m_d->activeMask->setEdgeDetectionSize(value); } void KisToolLazyBrushOptionsWidget::slotRadiusChanged(int value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask); m_d->activeMask->setFuzzyRadius(0.5 * value); } void KisToolLazyBrushOptionsWidget::slotCleanUpChanged(int value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask); m_d->activeMask->setCleanUpAmount(qreal(value) / 100.0); } void KisToolLazyBrushOptionsWidget::slotLimitToDeviceChanged(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask); m_d->activeMask->setLimitToDeviceBounds(value); }