diff --git a/libs/command/kis_command_ids.h b/libs/command/kis_command_ids.h index 4c60adcb58..a19705d09d 100644 --- a/libs/command/kis_command_ids.h +++ b/libs/command/kis_command_ids.h @@ -1,41 +1,42 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_COMMAND_IDS_H #define KIS_COMMAND_IDS_H namespace KisCommandUtils { enum CommandId { MoveShapeId = 9999, ResizeShapeId, TransformShapeId, ChangeShapeTransparencyId, ChangeShapeBackgroundId, ChangeShapeStrokeId, ChangeShapeMarkersId, ChangeShapeParameterId, ChangeEllipseShapeId, ChangeRectangleShapeId, ChangePathShapePointId, - ChangePathShapeControlPointId + ChangePathShapeControlPointId, + ChangePaletteId }; } #endif // KIS_COMMAND_IDS_H diff --git a/libs/koplugin/KisMimeDatabase.cpp b/libs/koplugin/KisMimeDatabase.cpp index 10fb99289c..e3fd692def 100644 --- a/libs/koplugin/KisMimeDatabase.cpp +++ b/libs/koplugin/KisMimeDatabase.cpp @@ -1,287 +1,292 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMimeDatabase.h" #include #include #include #include #include QList KisMimeDatabase::s_mimeDatabase; QString KisMimeDatabase::mimeTypeForFile(const QString &file, bool checkExistingFiles) { fillMimeData(); QFileInfo fi(file); QString suffix = fi.suffix().toLower(); Q_FOREACH(const KisMimeDatabase::KisMimeType &mimeType, s_mimeDatabase) { if (mimeType.suffixes.contains(suffix)) { debugPlugin << "mimeTypeForFile(). KisMimeDatabase returned" << mimeType.mimeType << "for" << file; return mimeType.mimeType; } } QMimeDatabase db; QMimeType mime; if (checkExistingFiles && fi.size() > 0) { mime = db.mimeTypeForFile(file, QMimeDatabase::MatchContent); if (mime.name() != "application/octet-stream" && mime.name() != "application/zip") { debugPlugin << "mimeTypeForFile(). QMimeDatabase returned" << mime.name() << "for" << file; return mime.name(); } } mime = db.mimeTypeForFile(file); if (mime.name() != "application/octet-stream") { debugPlugin << "mimeTypeForFile(). QMimeDatabase returned" << mime.name() << "for" << file; return mime.name(); } return ""; } QString KisMimeDatabase::mimeTypeForSuffix(const QString &suffix) { fillMimeData(); QMimeDatabase db; QString s = suffix.toLower(); Q_FOREACH(const KisMimeDatabase::KisMimeType &mimeType, s_mimeDatabase) { if (mimeType.suffixes.contains(s)) { debugPlugin << "mimeTypeForSuffix(). KisMimeDatabase returned" << mimeType.mimeType << "for" << s; return mimeType.mimeType; } } // make the file look like a file so Qt would recognize it s = "file." + s; return mimeTypeForFile(s); } QString KisMimeDatabase::mimeTypeForData(const QByteArray ba) { QMimeDatabase db; QMimeType mtp = db.mimeTypeForData(ba); debugPlugin << "mimeTypeForData(). QMimeDatabase returned" << mtp.name(); return mtp.name(); } QString KisMimeDatabase::descriptionForMimeType(const QString &mimeType) { fillMimeData(); Q_FOREACH(const KisMimeDatabase::KisMimeType &m, s_mimeDatabase) { if (m.mimeType == mimeType) { debugPlugin << "descriptionForMimeType. KisMimeDatabase returned" << m.description << "for" << mimeType; return m.description; } } QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); if (mime.name() != "application/octet-stream") { debugPlugin << "descriptionForMimeType. QMimeDatabase returned" << mime.comment() << "for" << mimeType; return mime.comment(); } return mimeType; } QStringList KisMimeDatabase::suffixesForMimeType(const QString &mimeType) { fillMimeData(); Q_FOREACH(const KisMimeDatabase::KisMimeType &m, s_mimeDatabase) { if (m.mimeType == mimeType) { debugPlugin << "suffixesForMimeType. KisMimeDatabase returned" << m.suffixes; return m.suffixes; } } QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); if (mime.name() != "application/octet-stream" && !mime.suffixes().isEmpty()) { QString preferredSuffix = mime.preferredSuffix(); if (mimeType == "image/x-tga") { preferredSuffix = "tga"; } if (mimeType == "image/jpeg") { preferredSuffix = "jpg"; } QStringList suffixes = mime.suffixes(); if (preferredSuffix != suffixes.first()) { suffixes.removeAll(preferredSuffix); suffixes.prepend(preferredSuffix); } debugPlugin << "suffixesForMimeType. QMimeDatabase returned" << suffixes; return suffixes; } return QStringList(); } QString KisMimeDatabase::iconNameForMimeType(const QString &mimeType) { QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); debugPlugin << "iconNameForMimeType" << mime.iconName(); return mime.iconName(); } void KisMimeDatabase::fillMimeData() { // This should come from the import/export plugins, but the json files aren't translated, // which is bad for the description field if (s_mimeDatabase.isEmpty()) { KisMimeType mimeType; mimeType.mimeType = "image/x-gimp-brush"; mimeType.description = i18nc("description of a file type", "Gimp Brush"); mimeType.suffixes = QStringList() << "gbr" << "vbr"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-gimp-brush-animated"; mimeType.description = i18nc("description of a file type", "Gimp Image Hose Brush"); mimeType.suffixes = QStringList() << "gih"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-adobe-brushlibrary"; mimeType.description = i18nc("description of a file type", "Adobe Brush Library"); mimeType.suffixes = QStringList() << "abr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-paintoppreset"; mimeType.description = i18nc("description of a file type", "Krita Brush Preset"); mimeType.suffixes = QStringList() << "kpp"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-assistant"; mimeType.description = i18nc("description of a file type", "Krita Assistant"); mimeType.suffixes = QStringList() << "paintingassistant"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-r32"; mimeType.description = i18nc("description of a file type", "R32 Heightmap"); mimeType.suffixes = QStringList() << "r32"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-r16"; mimeType.description = i18nc("description of a file type", "R16 Heightmap"); mimeType.suffixes = QStringList() << "r16"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-r8"; mimeType.description = i18nc("description of a file type", "R8 Heightmap"); mimeType.suffixes = QStringList() << "r8"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-spriter"; mimeType.description = i18nc("description of a file type", "Spriter SCML"); mimeType.suffixes = QStringList() << "scml"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-svm"; mimeType.description = i18nc("description of a file type", "Starview Metafile"); mimeType.suffixes = QStringList() << "svm"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/openraster"; mimeType.description = i18nc("description of a file type", "OpenRaster Image"); mimeType.suffixes = QStringList() << "ora"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-photoshop-style-library"; mimeType.description = i18nc("description of a file type", "Photoshop Layer Style Library"); mimeType.suffixes = QStringList() << "asl"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-color-palette"; mimeType.description = i18nc("description of a file type", "Color Palette"); mimeType.suffixes = QStringList() << "gpl" << "pal" << "act" << "aco" << "colors" << "xml" << "sbz"; s_mimeDatabase << mimeType; + mimeType.mimeType = "krita/x-colorset"; + mimeType.description = i18nc("description of a file type", "Krita Color Palette"); + mimeType.suffixes = QStringList() << "kpl"; + s_mimeDatabase << mimeType; + mimeType.mimeType = "application/x-opencolorio-configuration"; mimeType.description = i18nc("description of a file type", "OpenColorIO Configuration"); mimeType.suffixes = QStringList() << "ocio"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-gradient"; mimeType.description = i18nc("description of a file type", "GIMP Gradients"); mimeType.suffixes = QStringList() << "ggr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-gimp-pattern"; mimeType.description = i18nc("description of a file type", "GIMP Patterns"); mimeType.suffixes = QStringList() << "pat"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-karbon-gradient"; mimeType.description = i18nc("description of a file type", "Karbon Gradients"); mimeType.suffixes = QStringList() << "kgr"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-bundle"; mimeType.description = i18nc("description of a file type", "Krita Resource Bundle"); mimeType.suffixes = QStringList() << "bundle"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-workspace"; mimeType.description = i18nc("description of a file type", "Krita Workspace"); mimeType.suffixes = QStringList() << "kws"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-taskset"; mimeType.description = i18nc("description of a file type", "Krita Taskset"); mimeType.suffixes = QStringList() << "kts"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-krita-reference-images"; mimeType.description = i18nc("description of a file type", "Krita Reference Image Collection"); mimeType.suffixes = QStringList() << "krf"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-krita-raw"; mimeType.description = i18nc("description of a file type", "Camera Raw Files"); mimeType.suffixes = QStringList() << "bay" << "bmq" << "cr2" << "crw" << "cs1" << "dc2" << "dcr" << "dng" << "erf" << "fff" << "hdr" << "k25" << "kdc" << "mdc" << "mos" << "mrw" << "nef" << "orf" << "pef" << "pxn" << "raf" << "raw" << "rdc" << "sr2" << "srf" << "x3f" << "arw" << "3fr" << "cine" << "ia" << "kc2" << "mef" << "nrw" << "qtk" << "rw2" << "sti" << "rwl" << "srw"; s_mimeDatabase << mimeType; mimeType.mimeType = "application/x-extension-exr"; mimeType.description = i18nc("description of a file type", "OpenEXR (Extended)"); mimeType.suffixes = QStringList() << "exr"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/x-psb"; mimeType.description = i18nc("description of a file type", "Photoshop Image (Large)"); mimeType.suffixes = QStringList() << "psb"; s_mimeDatabase << mimeType; mimeType.mimeType = "image/heic"; mimeType.description = i18nc("description of a file type", "HEIC/HEIF Image"); mimeType.suffixes = QStringList() << "heic" << "heif"; s_mimeDatabase << mimeType; debugPlugin << "Filled mimedatabase with" << s_mimeDatabase.count() << "special mimetypes"; } } diff --git a/libs/libkis/Palette.cpp b/libs/libkis/Palette.cpp index 8ccde32577..a298aaf4b2 100644 --- a/libs/libkis/Palette.cpp +++ b/libs/libkis/Palette.cpp @@ -1,158 +1,163 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Palette.h" #include +#include +#include #include +#include struct Palette::Private { KoColorSet *palette {0}; }; Palette::Palette(Resource *resource): d(new Private()) { d->palette = dynamic_cast(resource->resource()); } Palette::~Palette() { delete d; } int Palette::numberOfEntries() const { if (!d->palette) return 0; - return d->palette->nColors(); + return d->palette->colorCount(); } int Palette::columnCount() { if (!d->palette) return 0; return d->palette->columnCount(); } void Palette::setColumnCount(int columns) { if (d->palette) d->palette->setColumnCount(columns); } QString Palette::comment() { if (!d->palette) return ""; return d->palette->comment(); } void Palette::setComment(QString comment) { if (!d->palette) return; return d->palette->setComment(comment); } -QStringList Palette::groupNames() +QStringList Palette::groupNames() const { if (!d->palette) return QStringList(); return d->palette->getGroupNames(); } bool Palette::addGroup(QString name) { if (!d->palette) return false; return d->palette->addGroup(name); } bool Palette::removeGroup(QString name, bool keepColors) { if (!d->palette) return false; return d->palette->removeGroup(name, keepColors); } int Palette::colorsCountTotal() { if (!d->palette) return 0; - return d->palette->nColors(); + return d->palette->colorCount(); } -int Palette::colorsCountGroup(QString name) +KisSwatch Palette::colorSetEntryByIndex(int index) { - if (!d->palette) return 0; - return d->palette->nColorsGroup(name); -} - -KoColorSetEntry Palette::colorSetEntryByIndex(int index) -{ - if (!d->palette) return KoColorSetEntry(); - return d->palette->getColorGlobal(index); + if (!d->palette) return KisSwatch(); + int col = index % columnCount(); + int row = (index - col) / columnCount(); + return d->palette->getColorGlobal(col, row); + return KisSwatch(); } -KoColorSetEntry Palette::colorSetEntryFromGroup(int index, const QString &groupName) +KisSwatch Palette::colorSetEntryFromGroup(int index, const QString &groupName) { - if (!d->palette) return KoColorSetEntry(); - - return d->palette->getColorGroup(index, groupName); + if (!d->palette) return KisSwatch(); + int row = index % columnCount(); + return d->palette->getColorGroup((index - row) / columnCount(), row, groupName); } -ManagedColor *Palette::colorForEntry(KoColorSetEntry entry) +ManagedColor *Palette::colorForEntry(KisSwatch entry) { - if (!d->palette) return 0; + if (!d->palette) return Q_NULLPTR; ManagedColor *color = new ManagedColor(entry.color()); return color; } -void Palette::addEntry(KoColorSetEntry entry, QString groupName) +void Palette::addEntry(KisSwatch entry, QString groupName) { d->palette->add(entry, groupName); } void Palette::removeEntry(int index, const QString &groupName) { - d->palette->removeAt(index, groupName); -} - -void Palette::insertEntry(int index, KoColorSetEntry entry, QString groupName) -{ - d->palette->insertBefore(entry, index, groupName); -} + int col = index % columnCount(); + int tmp = index; + int row = (index - col) / columnCount(); + KisSwatchGroup *groupFoundIn = Q_NULLPTR; + Q_FOREACH(const QString &name, groupNames()) { + KisSwatchGroup *g = d->palette->getGroup(name); + tmp -= g->rowCount() * columnCount(); + if (tmp < 0) { + groupFoundIn = g; + break; + } + row -= g->rowCount(); -bool Palette::editEntry(int index, KoColorSetEntry entry, QString groupName) -{ - return d->palette->changeColorSetEntry(entry, groupName, index); + } + if (!groupFoundIn) { return; } + groupFoundIn->removeEntry(col, row); } bool Palette::changeGroupName(QString oldGroupName, QString newGroupName) { return d->palette->changeGroupName(oldGroupName, newGroupName); } bool Palette::moveGroup(const QString &groupName, const QString &groupNameInsertBefore) { return d->palette->moveGroup(groupName, groupNameInsertBefore); } bool Palette::save() { if (d->palette->filename().size()>0) { return d->palette->save(); } //if there's no filename the palette proly doesn't even exist... return false; } KoColorSet *Palette::colorSet() { return d->palette; } diff --git a/libs/libkis/Palette.h b/libs/libkis/Palette.h index 901fd60c5f..4fc1f30bee 100644 --- a/libs/libkis/Palette.h +++ b/libs/libkis/Palette.h @@ -1,204 +1,186 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef LIBKIS_PALETTE_H #define LIBKIS_PALETTE_H #include +#include + #include "kritalibkis_export.h" #include "libkis.h" #include "Resource.h" #include "KoColorSet.h" class ManagedColor; /** * @brief The Palette class * Palette is a resource object that stores organised color data. * It's purpose is to allow artists to save colors and store them. * * An example for printing all the palettes and the entries: * * @code import sys from krita import * resources = Application.resources("palette") for (k, v) in resources.items(): print(k) palette = Palette(v) for x in range(palette.numberOfEntries()): entry = palette.colorSetEntryByIndex(x) c = palette.colorForEntry(entry); print(x, entry.name(), entry.id(), entry.spotColor(), c.toQString()) * @endcode */ class KRITALIBKIS_EXPORT Palette : public QObject { public: Palette(Resource *resource); ~Palette() override; /** * @brief numberOfEntries * @return */ int numberOfEntries() const; /** * @brief columnCount * @return the amount of columns this palette is set to use. */ int columnCount(); /** * @brief setColumnCount * Set the amount of columns this palette should use. */ void setColumnCount(int columns); /** * @brief comment * @return the comment or description associated with the palette. */ QString comment(); /** * @brief setComment * set the comment or description associated with the palette. * @param comment */ void setComment(QString comment); /** * @brief groupNames * @return the list of group names. This is list is in the order these groups are in the file. */ - QStringList groupNames(); + QStringList groupNames() const; /** * @brief addGroup * @param name of the new group * @return whether adding the group was successful. */ bool addGroup(QString name); /** * @brief removeGroup * @param name the name of the group to remove. * @param keepColors whether or not to delete all the colors inside, or to move them to the default group. * @return */ bool removeGroup(QString name, bool keepColors = true); /** * @brief colorsCountTotal * @return the total amount of entries in the whole group */ int colorsCountTotal(); - /** - * @brief colorsCountGroup - * @param name of the group to check. Empty is the default group. - * @return the amount of colors within that group. - */ - int colorsCountGroup(QString name); /** * @brief colorSetEntryByIndex * get the colorsetEntry from the global index. * @param index the global index * @return the colorset entry */ - KoColorSetEntry colorSetEntryByIndex(int index); + KisSwatch colorSetEntryByIndex(int index); /** * @brief colorSetEntryFromGroup * @param index index in the group. * @param groupName the name of the group to get the color from. * @return the colorsetentry. */ - KoColorSetEntry colorSetEntryFromGroup(int index, const QString &groupName); + KisSwatch colorSetEntryFromGroup(int index, const QString &groupName); /** * @brief colorForEntry * special function to retrieve a ManagedColor object from the colorsetentry. * @param entry the entry * @return the ManagedColorObject */ - ManagedColor *colorForEntry(KoColorSetEntry entry); + ManagedColor *colorForEntry(KisSwatch entry); /** * @brief addEntry * add an entry to a group. Gets appended to the end. * @param entry the entry * @param groupName the name of the group to add to. */ - void addEntry(KoColorSetEntry entry, QString groupName = QString()); + void addEntry(KisSwatch entry, QString groupName = QString()); /** * @brief removeEntry * remove the entry at @param index from the group @param groupName. */ void removeEntry(int index, const QString &groupName); - /** - * @brief insertEntry - * like addentry, but allows you to pick the index to insertBefore. - * @param index - * @param entry - * @param groupName - */ - void insertEntry(int index, KoColorSetEntry entry, QString groupName = QString()); - /** - * @brief editEntry - * Changes the entry at @param index by replacing it with @param entry. - * @param groupName the group at which the index is. - * @return whether it was successful. - */ - bool editEntry (int index, KoColorSetEntry entry, QString groupName = QString()); + /** * @brief changeGroupName * change the group name. * @param oldGroupName the old groupname to change. * @param newGroupName the new name to change it into. * @return whether successful. Reasons for failure include not knowing have oldGroupName */ bool changeGroupName(QString oldGroupName, QString newGroupName); /** * @brief moveGroup * move the group to before groupNameInsertBefore. * @param groupName group to move. * @param groupNameInsertBefore group to inset before. * @return whether successful. Reasons for failure include either group not existing. */ bool moveGroup(const QString &groupName, const QString &groupNameInsertBefore = QString()); /** * @brief save * save the palette * @return whether it was successful. */ bool save(); private: friend class PaletteView; struct Private; Private *const d; /** * @brief colorSet * @return gives qa KoColorSet object back */ KoColorSet *colorSet(); }; #endif // LIBKIS_PALETTE_H diff --git a/libs/libkis/PaletteView.cpp b/libs/libkis/PaletteView.cpp index 29611cb19b..4baf186faa 100644 --- a/libs/libkis/PaletteView.cpp +++ b/libs/libkis/PaletteView.cpp @@ -1,83 +1,83 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include struct PaletteView::Private { KisPaletteModel *model = 0; KisPaletteView *widget = 0; bool allowPaletteModification = true; }; PaletteView::PaletteView(QWidget *parent) : QWidget(parent), d(new Private) { d->widget = new KisPaletteView(this); d->model = new KisPaletteModel(); d->widget->setPaletteModel(d->model); this->setLayout(new QVBoxLayout()); this->layout()->addWidget(d->widget); //forward signals. - connect(d->widget, SIGNAL(entrySelected(KoColorSetEntry)), - this, SIGNAL(entrySelectedForeGround(KoColorSetEntry))); - connect(d->widget, SIGNAL(entrySelectedBackGround(KoColorSetEntry)), - this, SIGNAL(entrySelectedBackGround(KoColorSetEntry))); + connect(d->widget, SIGNAL(entrySelected(KisSwatch)), + this, SIGNAL(entrySelectedForeGround(KisSwatch))); + connect(d->widget, SIGNAL(entrySelectedBackGround(KisSwatch)), + this, SIGNAL(entrySelectedBackGround(KisSwatch))); } PaletteView::~PaletteView() { delete d->model; } void PaletteView::setPalette(Palette *palette) { - d->model->setColorSet(palette->colorSet()); + d->model->setPalette(palette->colorSet()); d->widget->setPaletteModel(d->model); } bool PaletteView::addEntryWithDialog(ManagedColor *color) { if (d->model->colorSet()) { return d->widget->addEntryWithDialog(color->color()); } return false; } bool PaletteView::addGroupWithDialog() { if (d->model->colorSet()) { return d->widget->addGroupWithDialog(); } return false; } bool PaletteView::removeSelectedEntryWithDialog() { if (d->model->colorSet()) { return d->widget->removeEntryWithDialog(d->widget->currentIndex()); } return false; } void PaletteView::trySelectClosestColor(ManagedColor *color) { - d->widget->trySelectClosestColor(color->color()); + d->widget->selectClosestColor(color->color()); } diff --git a/libs/libkis/PaletteView.h b/libs/libkis/PaletteView.h index f0c81e7d56..e48e4fa892 100644 --- a/libs/libkis/PaletteView.h +++ b/libs/libkis/PaletteView.h @@ -1,98 +1,97 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef LIBKIS_PALETTE_VIEW_H #define LIBKIS_PALETTE_VIEW_H #include #include #include "kritalibkis_export.h" #include "libkis.h" #include "Palette.h" #include "ManagedColor.h" #include "KoColorSet.h" #include #include /** * @brief The PaletteView class is a wrapper around a MVC method for handling * palettes. This class shows a nice widget that can drag and drop, edit colors in a colorset * and will handle adding and removing entries if you'd like it to. */ class KRITALIBKIS_EXPORT PaletteView : public QWidget { Q_OBJECT public: PaletteView(QWidget *parent = 0); ~PaletteView(); public Q_SLOTS: /** * @brief setPalette * Set a new palette. * @param palette */ void setPalette(Palette *palette); /** * @brief addEntryWithDialog * This gives a simple dialog for adding colors, with options like * adding name, id, and to which group the color should be added. * @param color the default color to add * @return whether it was successful. */ bool addEntryWithDialog(ManagedColor *color); /** * @brief addGroupWithDialog * gives a little dialog to ask for the desired groupname. * @return whether this was successful. */ bool addGroupWithDialog(); /** * @brief removeSelectedEntryWithDialog * removes the selected entry. If it is a group, it pop up a dialog * asking whether the colors should also be removed. * @return whether this was successful */ bool removeSelectedEntryWithDialog(); /** * @brief trySelectClosestColor * tries to select the closest color to the one given. * It does not force a change on the active color. * @param color the color to compare to. */ void trySelectClosestColor(ManagedColor *color); Q_SIGNALS: /** * @brief entrySelectedForeGround * fires when a swatch is selected with leftclick. * @param entry */ - void entrySelectedForeGround(KoColorSetEntry entry); + void entrySelectedForeGround(KisSwatch entry); /** * @brief entrySelectedBackGround * fires when a swatch is selected with rightclick. * @param entry */ - void entrySelectedBackGround(KoColorSetEntry entry); + void entrySelectedBackGround(KisSwatch entry); private: struct Private; const QScopedPointer d; - }; #endif // LIBKIS_PALETTE_VIEW_H diff --git a/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.cpp index 4e5c027001..87c0516b6a 100644 --- a/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.cpp +++ b/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.cpp @@ -1,147 +1,151 @@ /* This file is part of the KDE project * Copyright (C) 2012 Dan Leinir Turthra Jensen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "PaletteColorsModel.h" #include #include #include class PaletteColorsModel::Private { public: Private() : colorSet(0) , view(0) {} KoColorSet* colorSet; KisViewManager* view; }; PaletteColorsModel::PaletteColorsModel(QObject *parent) : QAbstractListModel(parent) , d(new Private) { } PaletteColorsModel::~PaletteColorsModel() { delete d; } QHash PaletteColorsModel::roleNames() const { QHash roles; roles[ImageRole] = "image"; roles[TextRole] = "text"; return roles; } int PaletteColorsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; if (!d->colorSet) return 0; - return d->colorSet->nColors(); + return d->colorSet->colorCount(); } QVariant PaletteColorsModel::data(const QModelIndex &index, int role) const { QVariant result; + /* QColor color; if (index.isValid() && d->colorSet) { switch(role) { case ImageRole: - color = d->colorSet->getColorGlobal(index.row()).color().toQColor(); + color = d->colorSet->getColorGlobal(index.row(), index.column()).color().toQColor(); result = QString("image://color/%1,%2,%3,%4").arg(color.redF()).arg(color.greenF()).arg(color.blueF()).arg(color.alphaF()); break; case TextRole: - result = d->colorSet->getColorGlobal(index.row()).name(); + result = d->colorSet->getColorGlobal(index.row(), index.column()).name(); break; default: break; } } + */ + return result; } QVariant PaletteColorsModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation); QVariant result; if (section == 0) { switch(role) { case ImageRole: result = QString("Thumbnail"); break; case TextRole: result = QString("Name"); break; default: break; } } return result; } void PaletteColorsModel::setColorSet(QObject *newColorSet) { d->colorSet = qobject_cast(newColorSet); beginResetModel(); endResetModel(); emit colorSetChanged(); } QObject* PaletteColorsModel::colorSet() const { return d->colorSet; } QObject* PaletteColorsModel::view() const { return d->view; } void PaletteColorsModel::setView(QObject* newView) { d->view = qobject_cast( newView ); emit viewChanged(); } void PaletteColorsModel::activateColor(int index, bool setBackgroundColor) { if ( !d->view ) return; - if (index >= 0 && index < (int)d->colorSet->nColors()) - { + if (index >= 0 && index < (int)d->colorSet->colorCount()) { + /* if (setBackgroundColor) d->view->resourceProvider()->setBGColor(d->colorSet->getColorGlobal(index).color()); else d->view->resourceProvider()->setFGColor( d->colorSet->getColorGlobal(index).color()); emit colorChanged(d->colorSet->getColorGlobal(index).color().toQColor(), setBackgroundColor); + */ } } diff --git a/libs/pigment/CMakeLists.txt b/libs/pigment/CMakeLists.txt index 1d2c1a58f9..d41dfd9d0a 100644 --- a/libs/pigment/CMakeLists.txt +++ b/libs/pigment/CMakeLists.txt @@ -1,122 +1,123 @@ project(kritapigment) # we have to repeat platform specifics from top-level if (WIN32) include_directories(${CMAKE_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif () include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/resources ${CMAKE_CURRENT_SOURCE_DIR}/compositeops) set(FILE_OPENEXR_SOURCES) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() set(LINK_VC_LIB) if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR}) set(LINK_VC_LIB ${Vc_LIBRARIES}) ko_compile_for_all_implementations_no_scalar(__per_arch_factory_objs compositeops/KoOptimizedCompositeOpFactoryPerArch.cpp) message("Following objects are generated from the per-arch lib") message("${__per_arch_factory_objs}") endif() add_subdirectory(tests) add_subdirectory(benchmarks) set(kritapigment_SRCS DebugPigment.cpp KoBasicHistogramProducers.cpp KoColor.cpp KoColorDisplayRendererInterface.cpp KoColorConversionAlphaTransformation.cpp KoColorConversionCache.cpp KoColorConversions.cpp KoColorConversionSystem.cpp KoColorConversionTransformation.cpp KoColorProofingConversionTransformation.cpp KoColorConversionTransformationFactory.cpp KoColorModelStandardIds.cpp KoColorProfile.cpp KoColorSpace.cpp KoColorSpaceEngine.cpp KoColorSpaceFactory.cpp KoColorSpaceMaths.cpp KoColorSpaceRegistry.cpp KoColorProfileStorage.cpp KoColorTransformation.cpp KoColorTransformationFactory.cpp KoColorTransformationFactoryRegistry.cpp KoCompositeColorTransformation.cpp KoCompositeOp.cpp KoCompositeOpRegistry.cpp KoCopyColorConversionTransformation.cpp KoFallBackColorTransformation.cpp KoHistogramProducer.cpp KoMultipleColorConversionTransformation.cpp KoUniqueNumberForIdServer.cpp colorspaces/KoAlphaColorSpace.cpp colorspaces/KoLabColorSpace.cpp colorspaces/KoRgbU16ColorSpace.cpp colorspaces/KoRgbU8ColorSpace.cpp colorspaces/KoSimpleColorSpaceEngine.cpp compositeops/KoOptimizedCompositeOpFactory.cpp compositeops/KoOptimizedCompositeOpFactoryPerArch_Scalar.cpp ${__per_arch_factory_objs} colorprofiles/KoDummyColorProfile.cpp resources/KoAbstractGradient.cpp resources/KoColorSet.cpp - resources/KoColorSetEntry.cpp + resources/KisSwatch.cpp + resources/KisSwatchGroup.cpp resources/KoPattern.cpp resources/KoResource.cpp resources/KoMD5Generator.cpp resources/KoHashGeneratorProvider.cpp resources/KoStopGradient.cpp resources/KoSegmentGradient.cpp ) set (EXTRA_LIBRARIES ${LINK_OPENEXR_LIB} ${LINK_VC_LIB}) if(MSVC OR (WIN32 AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")) # avoid "cannot open file 'LIBC.lib'" error set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /NODEFAULTLIB:LIBC.LIB") endif() add_library(kritapigment SHARED ${kritapigment_SRCS}) generate_export_header(kritapigment) target_include_directories( kritapigment PUBLIC $ $ ) target_link_libraries( kritapigment PUBLIC kritaplugin kritastore kritaglobal ${EXTRA_LIBRARIES} KF5::I18n KF5::ConfigCore Qt5::Core Qt5::Gui Qt5::Xml ${WIN32_PLATFORM_NET_LIBS} ) set_target_properties(kritapigment PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritapigment ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/pigment/resources/KoColorSetEntry.cpp b/libs/pigment/resources/KisSwatch.cpp similarity index 56% rename from libs/pigment/resources/KoColorSetEntry.cpp rename to libs/pigment/resources/KisSwatch.cpp index 8ac5d764b9..47199897cf 100644 --- a/libs/pigment/resources/KoColorSetEntry.cpp +++ b/libs/pigment/resources/KisSwatch.cpp @@ -1,33 +1,71 @@ /* * 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; 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 "KoColorSetEntry.h" +#include "KisSwatch.h" -KoColorSetEntry::KoColorSetEntry() + +KisSwatch::KisSwatch() : m_spotColor(false) + , m_valid(false) { } -KoColorSetEntry::KoColorSetEntry(const KoColor &color, const QString &name) +KisSwatch::KisSwatch(const KoColor &color, const QString &name) : m_color(color) , m_name(name) , m_spotColor(false) + , m_valid(true) { } +void KisSwatch::setName(const QString &name) +{ + m_name = name; + m_valid = true; +} + +void KisSwatch::setId(const QString &id) +{ + m_id = id; + m_valid = true; +} + +void KisSwatch::setColor(const KoColor &color) +{ + m_color = color; + m_valid = true; +} + +void KisSwatch::setSpotColor(bool spotColor) +{ + m_spotColor = spotColor; + m_valid = true; +} + +KisSwatch &KisSwatch::operator =(const KisSwatch &source) +{ + if (&source == this) + return *this; + m_color = source.m_color; + m_id = source.m_id; + m_name = source.m_name; + m_spotColor = source.m_spotColor; + m_valid = source.m_valid; + return *this; +} diff --git a/libs/pigment/resources/KoColorSetEntry.h b/libs/pigment/resources/KisSwatch.h similarity index 73% rename from libs/pigment/resources/KoColorSetEntry.h rename to libs/pigment/resources/KisSwatch.h index 7ff11429a1..d77d207940 100644 --- a/libs/pigment/resources/KoColorSetEntry.h +++ b/libs/pigment/resources/KisSwatch.h @@ -1,60 +1,65 @@ /* * 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; 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 KOCOLORSETENTRY_H -#define KOCOLORSETENTRY_H +#ifndef KISSWATCH_H +#define KISSWATCH_H #include "kritapigment_export.h" #include #include "KoColor.h" -class KRITAPIGMENT_EXPORT KoColorSetEntry +class KRITAPIGMENT_EXPORT KisSwatch { public: - KoColorSetEntry(); - KoColorSetEntry(const KoColor &color, const QString &name); + KisSwatch(); + KisSwatch(const KoColor &color, const QString &name = QString()); public: QString name() const { return m_name; } - void setName(const QString &name) { m_name = name; } + void setName(const QString &name); QString id() const { return m_id; } - void setId(const QString &id) { m_id = id; } + void setId(const QString &id); KoColor color() const { return m_color; } - void setColor(const KoColor &color) { m_color = color; } + void setColor(const KoColor &color); bool spotColor() const { return m_spotColor; } - void setSpotColor(bool spotColor) { m_spotColor = spotColor; } + void setSpotColor(bool spotColor); + + bool isValid() const { return m_valid; } public: - bool operator==(const KoColorSetEntry& rhs) const { + bool operator==(const KisSwatch& rhs) const { return m_color == rhs.m_color && m_name == rhs.m_name; } + KisSwatch &operator =(const KisSwatch &source); + private: KoColor m_color; QString m_name; QString m_id; bool m_spotColor; + bool m_valid; }; -#endif // KOCOLORSETENTRY_H +#endif // KISSWATCH_H diff --git a/libs/pigment/resources/KisSwatchGroup.cpp b/libs/pigment/resources/KisSwatchGroup.cpp new file mode 100644 index 0000000000..d2780171d9 --- /dev/null +++ b/libs/pigment/resources/KisSwatchGroup.cpp @@ -0,0 +1,217 @@ +/* This file is part of the KDE project + Copyright (c) 2005 Boudewijn Rempt + Copyright (c) 2016 L. E. Segovia + Copyright (c) 2018 Michael Zhou + + 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 "KisSwatchGroup.h" + +struct KisSwatchGroup::Private { + typedef QMap Column; + + Private() + : name(QString()) + , colorMatrix(DEFAULT_COLUMN_COUNT) + , colorCount(0) + , rowCount(DEFAULT_ROW_COUNT) + { } + + static int DEFAULT_COLUMN_COUNT; + static int DEFAULT_ROW_COUNT; + + QString name; + QVector colorMatrix; + int colorCount; + int rowCount; +}; + +int KisSwatchGroup::Private::DEFAULT_COLUMN_COUNT = 16; +int KisSwatchGroup::Private::DEFAULT_ROW_COUNT = 20; + +KisSwatchGroup::KisSwatchGroup() + : d(new Private) +{ } + +KisSwatchGroup::~KisSwatchGroup() +{ } + +KisSwatchGroup::KisSwatchGroup(const KisSwatchGroup &rhs) + : d(new Private(*rhs.d)) +{ } + +KisSwatchGroup &KisSwatchGroup::operator =(const KisSwatchGroup &rhs) +{ + if (&rhs == this) { + return *this; + } + d.reset(new Private(*rhs.d)); + return *this; +} + +void KisSwatchGroup::setEntry(const KisSwatch &e, int column, int row) +{ + Q_ASSERT(column < d->colorMatrix.size() && column >= 0 && row >= 0); + if (row >= d->rowCount) { + setRowCount(row + 1); + } + if (!checkEntry(column, row)) { + d->colorCount++; + } + d->colorMatrix[column][row] = e; +} + +bool KisSwatchGroup::checkEntry(int column, int row) const +{ + if (row >= d->rowCount || column >= d->colorMatrix.size() || column < 0) { + return false; + } + if (!d->colorMatrix[column].contains(row)) { + return false; + } + return true; +} + +bool KisSwatchGroup::removeEntry(int column, int row) +{ + if (d->colorCount == 0) { + return false; + } + + if (row >= d->rowCount || column >= d->colorMatrix.size() || column < 0) { + return false; + } + + // QMap::remove returns 1 if key found else 0 + if (d->colorMatrix[column].remove(row)) { + d->colorCount -= 1; + return true; + } else { + return false; + } +} + +void KisSwatchGroup::setColumnCount(int columnCount) +{ + Q_ASSERT(columnCount >= 0); + + if (columnCount < d->colorMatrix.size()) { + int newColorCount = 0; + for (int i = 0; i < columnCount; i++ ) { + newColorCount += d->colorMatrix[i].size(); + } + d->colorCount = newColorCount; + } + d->colorMatrix.resize(columnCount); +} + +int KisSwatchGroup::columnCount() const { + return d->colorMatrix.size(); +} + +KisSwatch KisSwatchGroup::getEntry(int column, int row) const +{ + Q_ASSERT(checkEntry(column, row)); + return d->colorMatrix[column][row]; +} + +void KisSwatchGroup::addEntry(const KisSwatch &e) +{ + if (columnCount() == 0) { + setColumnCount(Private::DEFAULT_COLUMN_COUNT); + } + + if (d->colorCount == 0) { + setEntry(e, 0, 0); + return; + } + + int y = 0; + for (const Private::Column &c : d->colorMatrix) { + if (c.isEmpty()) { continue; } + if (y < c.lastKey()) { + y = c.lastKey(); + } + } + for (int x = d->colorMatrix.size() - 1; x >= 0; x--) { + if (checkEntry(x, y)) { + // if the last entry's at the rightmost column, + // add e to the leftmost column of the next row + // and increase row count + if (++x == d->colorMatrix.size()) { + x = 0; + y++; + } + // else just add it to the right + setEntry(e, x, y); + break; + } + } +} + +void KisSwatchGroup::clear() +{ + d->colorMatrix.clear(); +} + +void KisSwatchGroup::setRowCount(int newRowCount) +{ + d->rowCount = newRowCount; + for (Private::Column &c : d->colorMatrix) { + for (int k : c.keys()) { + if (k >= newRowCount) { + c.remove(k); + d->colorCount--; + } + } + } +} + +int KisSwatchGroup::rowCount() const +{ + return d->rowCount; +} + +int KisSwatchGroup::colorCount() const +{ + return d->colorCount; +} + +QList KisSwatchGroup::infoList() const +{ + QList res; + int column = 0; + for (const Private::Column &c : d->colorMatrix) { + int i = 0; + for (const KisSwatch &s : c.values()) { + SwatchInfo info = {d->name, s, c.keys()[i++], column}; + res.append(info); + } + column++; + } + return res; +} + +void KisSwatchGroup::setName(const QString &name) +{ + d->name = name; +} + +QString KisSwatchGroup::name() const +{ + return d->name; +} diff --git a/libs/pigment/resources/KisSwatchGroup.h b/libs/pigment/resources/KisSwatchGroup.h new file mode 100644 index 0000000000..de32624bdd --- /dev/null +++ b/libs/pigment/resources/KisSwatchGroup.h @@ -0,0 +1,127 @@ +/* This file is part of the KDE project + Copyright (c) 2005 Boudewijn Rempt + Copyright (c) 2016 L. E. Segovia + Copyright (c) 2018 Michael Zhou + + 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 KISSWATCHGROUP_H +#define KISSWATCHGROUP_H + +#include "KisSwatch.h" + +#include "kritapigment_export.h" + +#include +#include +#include +#include + +/** + * @brief The KisSwatchGroup class stores a matrix of color swatches + * swatches can accessed using (x, y) coordinates. + * x is the column number from left to right and y is the row number from top + * to bottom. + * Both x and y start at 0 + * there could be empty entries, so the checkEntry(int, int) method must used + * whenever you want to get an entry from the matrix + */ +class KRITAPIGMENT_EXPORT KisSwatchGroup +{ +public /* struct */: + struct SwatchInfo { + QString group; + KisSwatch swatch; + int row; + int column; + }; + +public: + KisSwatchGroup(); + ~KisSwatchGroup(); + KisSwatchGroup(const KisSwatchGroup &rhs); + KisSwatchGroup &operator =(const KisSwatchGroup &rhs); + +public /* methods */: + void setName(const QString &name); + QString name() const; + + void setColumnCount(int columnCount); + int columnCount() const; + + void setRowCount(int newRowCount); + int rowCount() const; + + int colorCount() const; + + QList infoList() const; + + /** + * @brief checkEntry + * checks if position x and y has a valid entry + * both x and y start from 0 + * @param x + * @param y + * @return true if there is a valid entry at position (x, y) + */ + bool checkEntry(int column, int row) const; + /** + * @brief setEntry + * sets the entry at position (x, y) to be e + * @param e + * @param x + * @param y + */ + void setEntry(const KisSwatch &e, int column, int row); + /** + * @brief getEntry + * used to get the swatch entry at position (x, y) + * there is an assertion to make sure that this position isn't empty, + * so checkEntry(int, int) must be used before this method to ensure + * a valid entry can be found + * @param x + * @param y + * @return the swatch entry at position (x, y) + */ + KisSwatch getEntry(int column, int row) const; + /** + * @brief removeEntry + * removes the entry at position (x, y) + * @param x + * @param y + * @return true if these is an entry at (x, y) + */ + bool removeEntry(int column, int row); + /** + * @brief addEntry + * adds the entry e to the right of the rightmost entry in the last row + * if the rightmost entry in the last row is in the right most column, + * add e to the leftmost column of a new row + * + * when column is set to 0, resize number of columns to default + * @param e + */ + void addEntry(const KisSwatch &e); + + void clear(); + +private /* member variables */: + struct Private; + QScopedPointer d; +}; + +#endif // KISSWATCHGROUP_H diff --git a/libs/pigment/resources/KoColorSet.cpp b/libs/pigment/resources/KoColorSet.cpp index 9bfda757a8..4267c95bcb 100644 --- a/libs/pigment/resources/KoColorSet.cpp +++ b/libs/pigment/resources/KoColorSet.cpp @@ -1,1577 +1,1610 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt Copyright (c) 2016 L. E. Segovia This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include -#include // qFromLittleEndian -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include #include -#include #include #include #include -#include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include // qFromLittleEndian #include #include #include -#include "KoColor.h" -#include "KoColorSetEntry.h" -#include "KoColorProfile.h" -#include "KoColorSpaceRegistry.h" -#include "KoColorModelStandardIds.h" - - -struct KoColorSet::Private { - KoColorSet::PaletteType paletteType; - QByteArray data; - QString comment; - qint32 columns {0}; // Set the default value that the GIMP uses... - QVector colors; //ungrouped colors - QStringList groupNames; //names of the groups, this is used to determine the order they are in. - QMap> groups; //grouped colors. -}; - -KoColorSet::PaletteType detectFormat(const QString &fileName, const QByteArray &ba) { - - QFileInfo fi(fileName); - - // .pal - if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) { - return KoColorSet::RIFF_PAL; - } - // .gpl - else if (ba.startsWith("GIMP Palette")) { - return KoColorSet::GPL; - } - // .pal - else if (ba.startsWith("JASC-PAL")) { - return KoColorSet::PSP_PAL; - } - else if (fi.suffix().toLower() == "aco") { - return KoColorSet::ACO; - } - else if (fi.suffix().toLower() == "act") { - return KoColorSet::ACT; - } - else if (fi.suffix().toLower() == "xml") { - return KoColorSet::XML; - } - else if (fi.suffix().toLower() == "kpl") { - return KoColorSet::KPL; - } - else if (fi.suffix().toLower() == "sbz") { - return KoColorSet::SBZ; - } - return KoColorSet::UNKNOWN; -} +#include +#include +#include +#include +#include +#include +#include "KisSwatch.h" + +#include "KoColorSet.h" +#include "KoColorSet_p.h" + +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"; KoColorSet::KoColorSet(const QString& filename) : KoResource(filename) - , d(new Private()) + , d(new Private(this)) { -} - -KoColorSet::KoColorSet() - : KoResource(QString()) - , d(new Private()) -{ - + if (!filename.isEmpty()) { + QFileInfo f(filename); + setIsEditable(f.isWritable()); + } } /// Create an copied palette KoColorSet::KoColorSet(const KoColorSet& rhs) - : QObject(0) - , KoResource(QString()) - , d(new Private()) + : QObject(Q_NULLPTR) + , KoResource(rhs) + , d(new Private(this)) { - setFilename(rhs.filename()); + d->paletteType = rhs.d->paletteType; + d->data = rhs.d->data; d->comment = rhs.d->comment; - d->columns = rhs.d->columns; - d->colors = rhs.d->colors; d->groupNames = rhs.d->groupNames; d->groups = rhs.d->groups; - setValid(true); + d->isGlobal = rhs.d->isGlobal; + d->isEditable = rhs.d->isEditable; } KoColorSet::~KoColorSet() -{ -} +{ } bool KoColorSet::load() { QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { warnPigment << "Can't open file " << filename(); return false; } - bool res = loadFromDevice(&file); + bool res = loadFromDevice(&file); file.close(); + if (!QFileInfo(filename()).isWritable()) { + setIsEditable(false); + } return res; } bool KoColorSet::loadFromDevice(QIODevice *dev) { if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); d->data = dev->readAll(); Q_ASSERT(d->data.size() != 0); - return init(); + return d->init(); } bool KoColorSet::save() { - QFile file(filename()); - if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - return false; + if (d->isGlobal) { + // save to resource dir + QFile file(filename()); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + return false; + } + saveToDevice(&file); + file.close(); + return true; + } else { + return true; // palette is not global, but still indicate that it's saved } - saveToDevice(&file); - file.close(); - return true; } bool KoColorSet::saveToDevice(QIODevice *dev) const { bool res; switch(d->paletteType) { case GPL: - res = saveGpl(dev); + res = d->saveGpl(dev); break; default: - res = saveKpl(dev); + res = d->saveKpl(dev); } if (res) { KoResource::saveToDevice(dev); } return res; } -bool KoColorSet::init() +QByteArray KoColorSet::toByteArray() const { - d->colors.clear(); // just in case this is a reload (eg by KoEditColorSetDialog), - d->groups.clear(); - d->groupNames.clear(); - - if (filename().isNull()) { - warnPigment << "Cannot load palette" << name() << "there is no filename set"; - return false; - } - if (d->data.isNull()) { - QFile file(filename()); - if (file.size() == 0) { - warnPigment << "Cannot load palette" << name() << "there is no data available"; - return false; - } - file.open(QIODevice::ReadOnly); - d->data = file.readAll(); - file.close(); + QBuffer s; + 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; +} - bool res = false; - d->paletteType = detectFormat(filename(), d->data); +bool KoColorSet::fromByteArray(QByteArray &data) +{ + QBuffer buf(&data); + buf.open(QIODevice::ReadOnly); + return loadFromDevice(&buf); +} + +KoColorSet::PaletteType KoColorSet::paletteType() const +{ + return d->paletteType; +} + +void KoColorSet::setPaletteType(PaletteType paletteType) +{ + d->paletteType = paletteType; + QString suffix; switch(d->paletteType) { case GPL: - res = loadGpl(); + suffix = ".gpl"; break; case ACT: - res = loadAct(); + suffix = ".act"; break; case RIFF_PAL: - res = loadRiff(); - break; case PSP_PAL: - res = loadPsp(); + suffix = ".pal"; break; case ACO: - res = loadAco(); + suffix = ".aco"; break; case XML: - res = loadXml(); + suffix = ".xml"; break; case KPL: - res = loadKpl(); + suffix = ".kpl"; break; case SBZ: - res = loadSbz(); + suffix = ".sbz"; break; default: - res = false; - } - setValid(res); - - if (d->columns == 0) { - d->columns = 10; - } - - QImage img(d->columns * 4, (d->colors.size() / d->columns) * 4, QImage::Format_ARGB32); - QPainter gc(&img); - gc.fillRect(img.rect(), Qt::darkGray); - int counter = 0; - for(int i = 0; i < d->columns; ++i) { - for (int j = 0; j < (d->colors.size() / d->columns); ++j) { - if (counter < d->colors.size()) { - QColor c = d->colors.at(counter).color().toQColor(); - gc.fillRect(i * 4, j * 4, 4, 4, c); - counter++; - } - else { - break; - } - } - } - setImage(img); - - // save some memory - d->data.clear(); - return res; -} - -bool KoColorSet::saveGpl(QIODevice *dev) const -{ - QTextStream stream(dev); - stream << "GIMP Palette\nName: " << name() << "\nColumns: " << d->columns << "\n#\n"; - - for (int i = 0; i < d->colors.size(); i++) { - const KoColorSetEntry& entry = d->colors.at(i); - QColor c = entry.color().toQColor(); - stream << c.red() << " " << c.green() << " " << c.blue() << "\t"; - if (entry.name().isEmpty()) - stream << "Untitled\n"; - else - stream << entry.name() << "\n"; - } - - return true; -} - -quint32 KoColorSet::nColors() -{ - if (d->colors.isEmpty()) return 0; - - quint32 total = d->colors.size(); - if (!d->groups.empty()) { - Q_FOREACH (const QVector &group, d->groups.values()) { - total += group.size(); - } - } - return total; -} - -quint32 KoColorSet::nColorsGroup(QString groupName) { - if (d->groups.contains(groupName)) { - return d->groups.value(groupName).size(); - } else if (groupName.isEmpty() && !d->colors.isEmpty()){ - return d->colors.size(); - } else { - return 0; + suffix = defaultFileExtension(); } + QStringList fileName = filename().split("."); + fileName.last() = suffix.replace(".", ""); + setFilename(fileName.join(".")); } -quint32 KoColorSet::getIndexClosestColor(const KoColor color, bool useGivenColorSpace) -{ - quint32 closestIndex = 0; - quint8 highestPercentage = 0; - quint8 testPercentage = 0; - KoColor compare = color; - for (quint32 i=0; idifference(compare.data(), entry.data())); - if (testPercentage>highestPercentage) - { - closestIndex = i; - highestPercentage = testPercentage; - } - } - return closestIndex; -} - -QString KoColorSet::closestColorName(const KoColor color, bool useGivenColorSpace) -{ - int i = getIndexClosestColor(color, useGivenColorSpace); - return getColorGlobal(i).name(); -} -void KoColorSet::add(const KoColorSetEntry & c, QString groupName) +quint32 KoColorSet::colorCount() const { - if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) { - d->groups[groupName].push_back(c); - } else { - d->colors.push_back(c); + int colorCount = d->groups[GLOBAL_GROUP_NAME].colorCount(); + for (KisSwatchGroup &g : d->groups.values()) { + colorCount += g.colorCount(); } + return colorCount; } -quint32 KoColorSet::insertBefore(const KoColorSetEntry &c, qint32 index, const QString &groupName) +void KoColorSet::add(const KisSwatch &c, const QString &groupName) { - quint32 newIndex = index; - if (d->groups.contains(groupName)) { - d->groups[groupName].insert(index, c); - } else if (groupName.isEmpty()){ - d->colors.insert(index, c); - } else { - warnPigment << "Couldn't find group to insert to"; - } - return newIndex; + KisSwatchGroup &modifiedGroup = d->groups.contains(groupName) + ? d->groups[groupName] : d->global(); + modifiedGroup.addEntry(c); } -void KoColorSet::removeAt(quint32 index, QString groupName) +void KoColorSet::setEntry(const KisSwatch &e, int x, int y, const QString &groupName) { - if (d->groups.contains(groupName)){ - if ((quint32)d->groups.value(groupName).size()>index) { - d->groups[groupName].remove(index); - } - } else { - if ((quint32)d->colors.size()>index) { - d->colors.remove(index); - } - } + KisSwatchGroup &modifiedGroup = d->groups.contains(groupName) + ? d->groups[groupName] : d->global(); + modifiedGroup.setEntry(e, x, y); } void KoColorSet::clear() { - d->colors.clear(); d->groups.clear(); + d->groupNames.clear(); + d->groups[GLOBAL_GROUP_NAME] = KisSwatchGroup(); + d->groupNames.append(GLOBAL_GROUP_NAME); } -KoColorSetEntry KoColorSet::getColorGlobal(quint32 index) -{ - KoColorSetEntry e; - quint32 groupIndex = index; - QString groupName = findGroupByGlobalIndex(index, &groupIndex); - e = getColorGroup(groupIndex, groupName); - return e; -} - -KoColorSetEntry KoColorSet::getColorGroup(quint32 index, QString groupName) +KisSwatch KoColorSet::getColorGlobal(quint32 x, quint32 y) const { - KoColorSetEntry e; - if (d->groups.contains(groupName)) { - if (nColorsGroup(groupName)>index) { - e = d->groups.value(groupName).at(index); - } else { - warnPigment<index) { - e = d->colors.at(index); + int yInGroup = y; + QString nameGroupFoundIn; + for (const QString &groupName : d->groupNames) { + if (yInGroup < d->groups[groupName].rowCount()) { + nameGroupFoundIn = groupName; + break; } else { - warnPigment<colors.size()<=*index) { - *index -= (quint32)d->colors.size(); - if (!d->groups.empty() || !d->groupNames.empty()) { - QStringList groupNames = getGroupNames(); - Q_FOREACH (QString name, groupNames) { - quint32 size = (quint32)d->groups.value(name).size(); - if (size<=*index) { - *index -= size; - } else { - groupName = name; - return groupName; - } - } - + yInGroup -= d->groups[groupName].rowCount(); } } - return groupName; + const KisSwatchGroup &groupFoundIn = nameGroupFoundIn == GLOBAL_GROUP_NAME + ? d->global() : d->groups[nameGroupFoundIn]; + Q_ASSERT(groupFoundIn.checkEntry(x, yInGroup)); + return groupFoundIn.getEntry(x, yInGroup); } -QString KoColorSet::findGroupByColorName(const QString &name, quint32 *index) +KisSwatch KoColorSet::getColorGroup(quint32 x, quint32 y, QString groupName) { - *index = 0; - QString groupName = QString(); - for (int i = 0; icolors.size(); i++) { - if(d->colors.at(i).name() == name) { - *index = (quint32)i; - return groupName; - } + KisSwatch e; + const KisSwatchGroup &sourceGroup = groupName == QString() + ? d->global() : d->groups[groupName]; + if (sourceGroup.checkEntry(x, y)) { + e = sourceGroup.getEntry(x, y); } - QStringList groupNames = getGroupNames(); - Q_FOREACH (QString name, groupNames) { - for (int i=0; igroups[name].size(); i++) { - if(d->groups[name].at(i).name() == name) { - *index = (quint32)i; - groupName = name; - return groupName; - } - } - } - return groupName; -} - -QString KoColorSet::findGroupByID(const QString &id, quint32 *index) { - *index = 0; - QString groupName = QString(); - for (int i = 0; icolors.size(); i++) { - if(d->colors.at(i).id() == id) { - *index = (quint32)i; - return groupName; - } - } - QStringList groupNames = getGroupNames(); - Q_FOREACH (QString name, groupNames) { - for (int i=0; igroups[name].size(); i++) { - if(d->groups[name].at(i).id() == id) { - *index = (quint32)i; - groupName = name; - return groupName; - } - } - } - return groupName; + return e; } QStringList KoColorSet::getGroupNames() { - if (d->groupNames.size()groups.size()) { + 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(QString oldGroupName, QString newGroupName) +bool KoColorSet::changeGroupName(const QString &oldGroupName, const QString &newGroupName) { - if (d->groupNames.contains(oldGroupName)==false) { + if (!d->groups.contains(oldGroupName)) { return false; } - QVector dummyList = d->groups.value(oldGroupName); + if (oldGroupName == newGroupName) { + return true; + } + d->groups[newGroupName] = d->groups[oldGroupName]; d->groups.remove(oldGroupName); - d->groups[newGroupName] = dummyList; + d->groups[newGroupName].setName(newGroupName); //rename the string in the stringlist; int index = d->groupNames.indexOf(oldGroupName); d->groupNames.replace(index, newGroupName); return true; } -bool KoColorSet::changeColorSetEntry(KoColorSetEntry entry, QString groupName, quint32 index) -{ - if (index>=nColorsGroup(groupName) || (d->groupNames.contains(groupName)==false && groupName.size()>0)) { - return false; - } - - if (groupName==QString()) { - d->colors[index] = entry; - } else { - d->groups[groupName][index] = entry; - } - return true; -} - void KoColorSet::setColumnCount(int columns) { - d->columns = columns; + d->groups[GLOBAL_GROUP_NAME].setColumnCount(columns); + for (KisSwatchGroup &g : d->groups.values()) { + g.setColumnCount(columns); + } } -int KoColorSet::columnCount() +int KoColorSet::columnCount() const { - return d->columns; + 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) || d->groupNames.contains(groupName)) { return false; } d->groupNames.append(groupName); - d->groups[groupName] = QVector(); + 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)==false || d->groupNames.contains(groupNameInsertBefore)==false) { return false; } - d->groupNames.removeAt(d->groupNames.indexOf(groupName)); - int index = d->groupNames.size(); - if (groupNameInsertBefore!=QString()) { - index = d->groupNames.indexOf(groupNameInsertBefore); + 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); } - d->groupNames.insert(index, groupName); return true; } bool KoColorSet::removeGroup(const QString &groupName, bool keepColors) { if (!d->groups.contains(groupName)) { return false; } - if (keepColors) { - for (int i = 0; igroups.value(groupName).size(); i++) { - d->colors.append(d->groups.value(groupName).at(i)); - } + + if (groupName == GLOBAL_GROUP_NAME) { + return false; } - for(int n = 0; ngroupNames.size(); n++) { - if (d->groupNames.at(n) == groupName) { - d->groupNames.removeAt(n); + + 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"); } -bool KoColorSet::loadGpl() +int KoColorSet::rowCount() const { - QString s = QString::fromUtf8(d->data.data(), d->data.count()); - - if (s.isEmpty() || s.isNull() || s.length() < 50) { - warnPigment << "Illegal Gimp palette file: " << filename(); - return false; - } - - quint32 index = 0; - - QStringList lines = s.split('\n', QString::SkipEmptyParts); - - if (lines.size() < 3) { - warnPigment << "Not enough lines in palette file: " << filename(); - return false; - } - - QString columns; - qint32 r, g, b; - KoColorSetEntry e; - - // Read name - - - if (!lines[0].startsWith("GIMP") || !lines[1].toLower().contains("name")) { - warnPigment << "Illegal Gimp palette file: " << filename(); - return false; - } - - setName(i18n(lines[1].split(":")[1].trimmed().toLatin1())); - - index = 2; - - // Read columns - if (lines[index].toLower().contains("columns")) { - columns = lines[index].split(":")[1].trimmed(); - d->columns = columns.toInt(); - index = 3; - } - - - for (qint32 i = index; i < lines.size(); i++) { - - if (lines[i].startsWith('#')) { - d->comment += lines[i].mid(1).trimmed() + ' '; - } else if (!lines[i].isEmpty()) { - QStringList a = lines[i].replace('\t', ' ').split(' ', QString::SkipEmptyParts); - - if (a.count() < 3) { - break; - } - - r = a[0].toInt(); - a.pop_front(); - g = a[0].toInt(); - a.pop_front(); - b = a[0].toInt(); - a.pop_front(); - - r = qBound(0, r, 255); - g = qBound(0, g, 255); - b = qBound(0, b, 255); - - e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); - - QString name = a.join(" "); - e.setName(name.isEmpty() ? i18n("Untitled") : name); - - add(e); - } + int res = 0; + for (const QString &name : d->groupNames) { + res += d->groups[name].rowCount(); } - return true; + return res; } -bool KoColorSet::loadAct() +KisSwatchGroup *KoColorSet::getGroup(const QString &name) { - QFileInfo info(filename()); - setName(info.baseName()); - KoColorSetEntry e; - for (int i = 0; i < d->data.size(); i += 3) { - quint8 r = d->data[i]; - quint8 g = d->data[i+1]; - quint8 b = d->data[i+2]; - e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); - add(e); + if (!d->groups.contains(name)) { + return Q_NULLPTR; } - return true; + return &(d->groups[name]); } -struct RiffHeader { - quint32 riff; - quint32 size; - quint32 signature; - quint32 data; - quint32 datasize; - quint16 version; - quint16 colorcount; -}; - - -bool KoColorSet::loadRiff() +KisSwatchGroup *KoColorSet::getGlobalGroup() { - // http://worms2d.info/Palette_file - QFileInfo info(filename()); - setName(info.baseName()); - KoColorSetEntry e; - - RiffHeader header; - memcpy(&header, d->data.constData(), sizeof(RiffHeader)); - header.colorcount = qFromBigEndian(header.colorcount); + return getGroup(GLOBAL_GROUP_NAME); +} - for (int i = sizeof(RiffHeader); - (i < (int)(sizeof(RiffHeader) + header.colorcount) && i < d->data.size()); - i += 4) { - quint8 r = d->data[i]; - quint8 g = d->data[i+1]; - quint8 b = d->data[i+2]; - e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8())); - add(e); - } - return true; +bool KoColorSet::isGlobal() const +{ + return d->isGlobal; } +void KoColorSet::setIsGlobal(bool isGlobal) +{ + d->isGlobal = isGlobal; +} -bool KoColorSet::loadPsp() +bool KoColorSet::isEditable() const { - QFileInfo info(filename()); - setName(info.baseName()); - KoColorSetEntry e; - qint32 r, g, b; + return d->isEditable; +} - QString s = QString::fromUtf8(d->data.data(), d->data.count()); - QStringList l = s.split('\n', QString::SkipEmptyParts); - if (l.size() < 4) return false; - if (l[0] != "JASC-PAL") return false; - if (l[1] != "0100") return false; +void KoColorSet::setIsEditable(bool isEditable) +{ + d->isEditable = isEditable; +} - int entries = l[2].toInt(); +KisSwatchGroup::SwatchInfo KoColorSet::getClosestColorInfo(KoColor compare, bool useGivenColorSpace) +{ + KisSwatchGroup::SwatchInfo res; - for (int i = 0; i < entries; ++i) { + quint8 highestPercentage = 0; + quint8 testPercentage = 0; - QStringList a = l[i + 3].replace('\t', ' ').split(' ', QString::SkipEmptyParts); + 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()); - if (a.count() != 3) { - continue; + } 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; +} - r = a[0].toInt(); - a.pop_front(); - g = a[0].toInt(); - a.pop_front(); - b = a[0].toInt(); - a.pop_front(); - - r = qBound(0, r, 255); - g = qBound(0, g, 255); - b = qBound(0, b, 255); +/********************************KoColorSet::Private**************************/ - e.setColor(KoColor(QColor(r, g, b), - KoColorSpaceRegistry::instance()->rgb8())); +KoColorSet::Private::Private(KoColorSet *a_colorSet) + : colorSet(a_colorSet) + , isGlobal(true) + , isEditable(false) +{ + groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup(); + groupNames.append(KoColorSet::GLOBAL_GROUP_NAME); +} - QString name = a.join(" "); - e.setName(name.isEmpty() ? i18n("Untitled") : name); +KoColorSet::PaletteType KoColorSet::Private::detectFormat(const QString &fileName, const QByteArray &ba) +{ + QFileInfo fi(fileName); - add(e); + // .pal + if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) { + return KoColorSet::RIFF_PAL; } - return true; + // .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 scribusParseColor(KoColorSet *set, QXmlStreamReader *xml) +void KoColorSet::Private::scribusParseColor(KoColorSet *set, QXmlStreamReader *xml) { - KoColorSetEntry colorEntry; + 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; + 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); + QPainter gc(&img); + 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); + } + colorSet->setImage(img); + colorSet->setValid(res); - currentColor.data()[0] = c; - currentColor.data()[1] = m; - currentColor.data()[2] = y; - currentColor.data()[3] = k; - currentColor.setOpacity(OPACITY_OPAQUE_U8); - colorEntry.setColor(currentColor); + data.clear(); + return res; +} - set->add(colorEntry); +bool KoColorSet::Private::saveGpl(QIODevice *dev) const +{ + Q_ASSERT(dev->isOpen()); + Q_ASSERT(dev->isWritable()); - while(xml->readNextStartElement()) { - //ignore - these are all unknown or the /> element tag - xml->skipCurrentElement(); + 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; } - return; + 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"; } } - else { - xml->raiseError("Unknown color space for color " + colorEntry.name()); - } + + return true; } -bool loadScribusXmlPalette(KoColorSet *set, QXmlStreamReader *xml) +bool KoColorSet::Private::loadGpl() { + QString s = QString::fromUtf8(data.data(), data.count()); - //1. Get name - QXmlStreamAttributes paletteProperties = xml->attributes(); - QStringRef paletteName = paletteProperties.value("Name"); - dbgPigment << "Processed name of palette:" << paletteName; - set->setName(paletteName.toString()); + if (s.isEmpty() || s.isNull() || s.length() < 50) { + warnPigment << "Illegal Gimp palette file: " << colorSet->filename(); + return false; + } - //2. Inside the SCRIBUSCOLORS, there are lots of colors. Retrieve them + quint32 index = 0; - while(xml->readNextStartElement()) { - QStringRef currentElement = xml->name(); - if(QStringRef::compare(currentElement, "COLOR", Qt::CaseInsensitive) == 0) { - scribusParseColor(set, xml); - } - else { - xml->skipCurrentElement(); - } + QStringList lines = s.split('\n', QString::SkipEmptyParts); + + if (lines.size() < 3) { + warnPigment << "Not enough lines in palette file: " << colorSet->filename(); + return false; } - if(xml->hasError()) { + 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; } - return true; -} + colorSet->setName(i18n(lines[1].split(":")[1].trimmed().toLatin1())); -bool KoColorSet::loadXml() { - bool res = false; + index = 2; - QXmlStreamReader *xml = new QXmlStreamReader(d->data); + // Read columns + int columns = 0; + if (lines[index].toLower().contains("columns")) { + columnsText = lines[index].split(":")[1].trimmed(); + columns = columnsText.toInt(); + global().setColumnCount(columns); + index = 3; + } - if (xml->readNextStartElement()) { - QStringRef paletteId = xml->name(); - if (QStringRef::compare(paletteId, "SCRIBUSCOLORS", Qt::CaseInsensitive) == 0) { // Scribus - dbgPigment << "XML palette: " << filename() << ", Scribus format"; - res = loadScribusXmlPalette(this, xml); - } - else { - // Unknown XML format - xml->raiseError("Unknown XML palette format. Expected SCRIBUSCOLORS, found " + paletteId); + + 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) { + break; + } + + 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() ? i18n("Untitled") : name); + + global().addEntry(e); } } + int rowCount = global().colorCount()/ global().columnCount(); + if (global().colorCount() % global().columnCount()>0) { + rowCount ++; + } + global().setRowCount(rowCount); + return true; +} - // If there is any error (it should be returned through the stream) - if (xml->hasError() || !res) { - warnPigment << "Illegal XML palette:" << filename(); - warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString(); - return false; +bool KoColorSet::Private::loadAct() +{ + QFileInfo info(colorSet->filename()); + colorSet->setName(info.baseName()); + 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); } - else { - dbgPigment << "XML palette parsed successfully:" << filename(); - return true; + return true; +} + +bool KoColorSet::Private::loadRiff() +{ + // http://worms2d.info/Palette_file + QFileInfo info(colorSet->filename()); + colorSet->setName(info.baseName()); + 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::saveKpl(QIODevice *dev) const + +bool KoColorSet::Private::loadPsp() { - QScopedPointer store(KoStore::createStore(dev, KoStore::Write, "application/x-krita-palette", KoStore::Zip)); - if (!store || store->bad()) return false; + QFileInfo info(colorSet->filename()); + colorSet->setName(info.baseName()); + KisSwatch e; + qint32 r, g, b; - QSet profiles; - QMap profileMap; + QString s = QString::fromUtf8(data.data(), data.count()); + QStringList l = s.split('\n', QString::SkipEmptyParts); + if (l.size() < 4) return false; + if (l[0] != "JASC-PAL") return false; + if (l[1] != "0100") return false; - { - QDomDocument doc; - QDomElement root = doc.createElement("Colorset"); - root.setAttribute("version", "1.0"); - root.setAttribute("name", name()); - root.setAttribute("comment", d->comment); - root.setAttribute("columns", d->columns); - Q_FOREACH(const KoColorSetEntry &entry, d->colors) { - - // Only save non-builtin profiles.= - const KoColorProfile *profile = entry.color().colorSpace()->profile(); - if (!profile->fileName().isEmpty()) { - profiles << profile; - profileMap[profile] = entry.color().colorSpace(); - } - QDomElement el = doc.createElement("ColorSetEntry"); - el.setAttribute("name", entry.name()); - el.setAttribute("id", entry.id()); - el.setAttribute("spot", entry.spotColor() ? "true" : "false"); - el.setAttribute("bitdepth", entry.color().colorSpace()->colorDepthId().id()); - entry.color().toXML(doc, el); - root.appendChild(el); - } - Q_FOREACH(const QString &groupName, d->groupNames) { - QDomElement gl = doc.createElement("Group"); - gl.setAttribute("name", groupName); - root.appendChild(gl); - Q_FOREACH(const KoColorSetEntry &entry, d->groups.value(groupName)) { + int entries = l[2].toInt(); - // Only save non-builtin profiles.= - const KoColorProfile *profile = entry.color().colorSpace()->profile(); - if (!profile->fileName().isEmpty()) { - profiles << profile; - profileMap[profile] = entry.color().colorSpace(); - } - QDomElement el = doc.createElement("ColorSetEntry"); - el.setAttribute("name", entry.name()); - el.setAttribute("id", entry.id()); - el.setAttribute("spot", entry.spotColor() ? "true" : "false"); - el.setAttribute("bitdepth", entry.color().colorSpace()->colorDepthId().id()); - entry.color().toXML(doc, el); - gl.appendChild(el); - } + for (int i = 0; i < entries; ++i) { + + QStringList a = l[i + 3].replace('\t', ' ').split(' ', QString::SkipEmptyParts); + + if (a.count() != 3) { + continue; } - 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; } - } + r = qBound(0, a[0].toInt(), 255); + g = qBound(0, a[1].toInt(), 255); + b = qBound(0, a[2].toInt(), 255); - QDomDocument doc; - QDomElement profileElement = doc.createElement("Profiles"); + e.setColor(KoColor(QColor(r, g, b), + KoColorSpaceRegistry::instance()->rgb8())); - Q_FOREACH(const KoColorProfile *profile, profiles) { - QString fn = QFileInfo(profile->fileName()).fileName(); - if (!store->open(fn)) { return false; } - QByteArray profileRawData = profile->rawData(); - if (!store->write(profileRawData)) { return false; } - if (!store->close()) { return false; } - QDomElement el = doc.createElement("Profile"); - el.setAttribute("filename", fn); - el.setAttribute("name", profile->name()); - el.setAttribute("colorModelId", profileMap[profile]->colorModelId().id()); - el.setAttribute("colorDepthId", profileMap[profile]->colorDepthId().id()); - profileElement.appendChild(el); + QString name = a.join(" "); + e.setName(name.isEmpty() ? i18n("Untitled") : name); + groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e); } - 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(); + return true; } -bool KoColorSet::loadKpl() +bool KoColorSet::Private::loadKpl() { - QBuffer buf(&d->data); + QBuffer buf(&data); buf.open(QBuffer::ReadOnly); - QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-krita-palette", KoStore::Zip)); - if (!store || store->bad()) return false; + 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("Profiles"); + QDomElement c = e.firstChildElement(KPL_PALETTE_PROFILE_TAG); while (!c.isNull()) { - - QString name = c.attribute("name"); - QString filename = c.attribute("filename"); - QString colorModelId = c.attribute("colorModelId"); - QString colorDepthId = c.attribute("colorDepthId"); + 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(); QDomDocument doc; doc.setContent(ba); QDomElement e = doc.documentElement(); - setName(e.attribute("name")); - d->comment = e.attribute("comment"); - d->columns = e.attribute("columns").toInt(); - - QDomElement c = e.firstChildElement("ColorSetEntry"); - while (!c.isNull()) { - QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id()); - KoColorSetEntry entry; - - - entry.setColor(KoColor::fromXML(c.firstChildElement(), colorDepthId)); - entry.setName(c.attribute("name")); - entry.setId(c.attribute("id")); - entry.setSpotColor(c.attribute("spot", "false") == "true" ? true : false); - d->colors << entry; + colorSet->setName(e.attribute(KPL_PALETTE_NAME_ATTR)); + colorSet->setColumnCount(e.attribute(KPL_PALETTE_COLUMN_COUNT_ATTR).toInt()); + colorSet->setIsEditable(e.attribute(KPL_PALETTE_READONLY_ATTR) != "true"); + comment = e.attribute(KPL_PALETTE_COMMENT_ATTR); - c = c.nextSiblingElement("ColorSetEntry"); + loadKplGroup(doc, e, colorSet->getGlobalGroup()); - } - QDomElement g = e.firstChildElement("Group"); + QDomElement g = e.firstChildElement(KPL_GROUP_TAG); while (!g.isNull()) { - QString groupName = g.attribute("name"); - addGroup(groupName); - QDomElement cg = g.firstChildElement("ColorSetEntry"); - while (!cg.isNull()) { - QString colorDepthId = cg.attribute("bitdepth", Integer8BitsColorDepthID.id()); - KoColorSetEntry entry; - - - entry.setColor(KoColor::fromXML(cg.firstChildElement(), colorDepthId)); - entry.setName(cg.attribute("name")); - entry.setId(cg.attribute("id")); - entry.setSpotColor(cg.attribute("spot", "false") == "true" ? true : false); - add(entry, groupName); - - cg = cg.nextSiblingElement("ColorSetEntry"); - - } - g = g.nextSiblingElement("Group"); + 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; } -quint16 readShort(QIODevice *io) { - quint16 val; - quint64 read = io->read((char*)&val, 2); - if (read != 2) return false; - return qFromBigEndian(val); -} - -bool KoColorSet::loadAco() +bool KoColorSet::Private::loadAco() { - QFileInfo info(filename()); - setName(info.baseName()); + QFileInfo info(colorSet->filename()); + colorSet->setName(info.baseName()); - QBuffer buf(&d->data); + QBuffer buf(&data); buf.open(QBuffer::ReadOnly); quint16 version = readShort(&buf); quint16 numColors = readShort(&buf); - KoColorSetEntry e; + 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" << filename() << "(" << colorSpace << ")"; + 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" << filename(); + 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) { - add(e); + groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e); } } return true; } -bool KoColorSet::loadSbz() { - QBuffer buf(&d->data); +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: " << filename() << ", SwatchBooker format"; + 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:" << filename(); + 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; - setName(colorName); - dbgPigment << "Processed name of palette:" << name(); + 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 materialsBook; QHash fileColorSpaces; // Color processing for(; !colorElement.isNull(); colorElement = colorElement.nextSiblingElement("color")) { - KoColorSetEntry currentEntry; + 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)) { - add(materialsBook.value(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)) { - add(materialsBook.value(id), currentGroupName); + 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"); + 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()/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 e8ba024596..60ca58b30a 100644 --- a/libs/pigment/resources/KoColorSet.h +++ b/libs/pigment/resources/KoColorSet.h @@ -1,219 +1,216 @@ /* This file is part of the KDE project Copyright (c) 2005 Boudewijn Rempt Copyright (c) 2016 L. E. Segovia This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KOCOLORSET #define KOCOLORSET #include #include #include #include #include #include "KoColor.h" -#include "KoColorSetEntry.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 QObject, public KoResource { Q_OBJECT 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); - - /// Create an empty color set - KoColorSet(); + explicit KoColorSet(const QString &filename = QString()); - /// Explicit copy constructor (KoResource copy constructor is private) + // Explicit copy constructor (KoResource copy constructor is private) KoColorSet(const KoColorSet& rhs); +public /* overridden methods */: // KoResource ~KoColorSet() override; bool load() override; bool loadFromDevice(QIODevice *dev) override; bool save() override; bool saveToDevice(QIODevice* dev) const override; - QString defaultFileExtension() const override; + +public /* methods */: void setColumnCount(int columns); - int columnCount(); - /** - * @brief comment - * @return the comment. - */ - QString comment(); + int columnCount() const; void setComment(QString comment); + QString comment(); -public: + int rowCount() const; + quint32 colorCount() const; + + PaletteType paletteType() const; + void setPaletteType(PaletteType paletteType); /** - * @brief add Add a color to the palette. - * @param groupName color to add the group to. If empty, it will be added to the unsorted. + * @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 */ - void add(const KoColorSetEntry &, QString groupName = QString()); + bool isGlobal() const; + void setIsGlobal(bool); + + bool isEditable() const; + void setIsEditable(bool isEditable); + + QByteArray toByteArray() const; + bool fromByteArray(QByteArray &data); /** - * @brief insertBefore insert color before index into group. - * @param index - * @param groupName name of the group that the color goes into. - * @return new index of index after the prepending. + * @brief add Add a color to the palette. + * @param groupName color to add the group to. If empty, it will be added to the unsorted. */ - quint32 insertBefore(const KoColorSetEntry &, qint32 index, const QString &groupName = QString()); - - void removeAt(quint32 index, QString groupName = QString()); + 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 itterating through all color entries. * @param globalIndex the global index over the whole palette. * @return the entry. */ - KoColorSetEntry getColorGlobal(quint32 globalIndex); + KisSwatch getColorGlobal(quint32 x, quint32 y) const; + /** * @brief getColorGroup * A function for getting the color from a specific group. * @param groupName the name of the group, will give unosrted when not defined. * @param index the index within the group. * @return the entry */ - KoColorSetEntry getColorGroup(quint32 index, QString groupName = QString()); - - QString findGroupByGlobalIndex(quint32 globalIndex, quint32 *index); - QString findGroupByColorName(const QString &name, quint32 *index); - QString findGroupByID(const QString &id,quint32 *index); + KisSwatch getColorGroup(quint32 x, quint32 y, QString groupName); /** * @brief getGroupNames * @return returns a list of group names, excluding the unsorted group. */ QStringList getGroupNames(); - bool changeGroupName(QString oldGroupName, QString newGroupName); - - bool changeColorSetEntry(KoColorSetEntry entry, QString groupName, quint32 index); - /** - * @brief nColorsGroup - * @param groupName string name of the group, when not specified, returns unsorted colors. - * @return the amount of colors in this group. + * @brief getGroup + * @param name + * @return the group with the name given; global group if no parameter is given + * null pointer if not found. */ - quint32 nColorsGroup(QString groupName = QString()); - /** - * @brief nColors - * @return total colors in palette. - */ - quint32 nColors(); + 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 = QString()); + 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 color the color you wish to compare. * @param useGivenColorSpace whether to use the color space of the color given * when the two colors' colorspaces don't match. Else it'll use the entry's colorspace. * @return returns the int of the closest match. */ - quint32 getIndexClosestColor(KoColor color, bool useGivenColorSpace = true); - - /** - * @brief closestColorName - * convenience function to get the name of the closest match. - * @param color - * @param useGivenColorSpace - * @return - */ - QString closestColorName(KoColor color, bool useGivenColorSpace = true); + KisSwatchGroup::SwatchInfo getClosestColorInfo(KoColor compare, bool useGivenColorSpace = true); private: - - - bool init(); - - bool saveGpl(QIODevice *dev) const; - bool loadGpl(); - - bool loadAct(); - bool loadRiff(); - bool loadPsp(); - bool loadAco(); - bool loadXml(); - bool loadSbz(); - - bool saveKpl(QIODevice *dev) const; - bool loadKpl(); - - - - struct Private; + class Private; const QScopedPointer d; }; #endif // KOCOLORSET - diff --git a/libs/pigment/resources/KoColorSet_p.h b/libs/pigment/resources/KoColorSet_p.h new file mode 100644 index 0000000000..3659d88aef --- /dev/null +++ b/libs/pigment/resources/KoColorSet_p.h @@ -0,0 +1,74 @@ +#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; + 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; + bool isEditable; + +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/KoResource.cpp b/libs/pigment/resources/KoResource.cpp index b0f0485275..4e1aa15e4b 100644 --- a/libs/pigment/resources/KoResource.cpp +++ b/libs/pigment/resources/KoResource.cpp @@ -1,166 +1,164 @@ /* 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" struct Q_DECL_HIDDEN KoResource::Private { QString name; QString filename; bool valid; bool removable; QByteArray md5; QImage image; bool permanent; }; KoResource::KoResource(const QString& filename) : d(new Private) { d->filename = filename; d->valid = false; QFileInfo fileInfo(filename); d->removable = fileInfo.isWritable(); d->permanent = false; } KoResource::~KoResource() { delete d; } KoResource::KoResource(const KoResource &rhs) : d(new Private(*rhs.d)) -{ - qDebug() << ">>>>>>>>>>>>>>>>>>" << filename() << name() << valid(); -} +{ } bool KoResource::saveToDevice(QIODevice *dev) const { Q_UNUSED(dev) d->md5 = QByteArray(); return true; } QImage KoResource::image() const { return d->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 = hashGenerator->generateHash(d->filename); if (hash.isEmpty()) { QByteArray ba; QBuffer buf(&ba); buf.open(QBuffer::WriteOnly); if (saveToDevice(&buf)) { buf.close(); hash = hashGenerator->generateHash(ba); } } return hash; } QString KoResource::filename() const { return d->filename; } void KoResource::setFilename(const QString& filename) { d->filename = filename; QFileInfo fileInfo(filename); d->removable = ! fileInfo.exists() || fileInfo.isWritable(); } QString KoResource::shortFilename() const { QFileInfo fileInfo(d->filename); return fileInfo.fileName(); } QString KoResource::name() const { return d->name; } void KoResource::setName(const QString& name) { d->name = name; } bool KoResource::valid() const { return d->valid; } void KoResource::setValid(bool valid) { d->valid = valid; } bool KoResource::removable() const { return d->removable; } QString KoResource::defaultFileExtension() const { return QString(); } bool KoResource::permanent() const { return d->permanent; } void KoResource::setPermanent(bool permanent) { d->permanent = permanent; } diff --git a/libs/pigment/tests/CMakeLists.txt b/libs/pigment/tests/CMakeLists.txt index 910e13e091..db0a659a15 100644 --- a/libs/pigment/tests/CMakeLists.txt +++ b/libs/pigment/tests/CMakeLists.txt @@ -1,38 +1,38 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/") include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../colorspaces ) if(MSVC OR (WIN32 AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")) # avoid "cannot open file 'LIBC.lib'" error set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /NODEFAULTLIB:LIBC.LIB") endif() ecm_add_tests( TestKoColorSpaceRegistry.cpp TestKoColorSpaceAbstract.cpp TestColorConversionSystem.cpp TestKoColor.cpp TestKoIntegerMaths.cpp TestConvolutionOpImpl.cpp KoRgbU8ColorSpaceTester.cpp TestKoColorSpaceSanity.cpp TestFallBackColorTransformation.cpp TestKoChannelInfo.cpp NAME_PREFIX "libs-pigment-" LINK_LIBRARIES kritapigment KF5::I18n Qt5::Test) ecm_add_tests( TestColorConversion.cpp TestKoColorSpaceMaths.cpp + TestKisSwatchGroup.cpp + # TestKoColorSet.cpp NAME_PREFIX "libs-pigment-" LINK_LIBRARIES kritapigment Qt5::Test) - - add_executable(CCSGraph CCSGraph.cpp) target_link_libraries(CCSGraph kritapigment KF5::I18n) ecm_mark_as_test(CCSGraph) diff --git a/libs/pigment/tests/TestKisSwatchGroup.cpp b/libs/pigment/tests/TestKisSwatchGroup.cpp new file mode 100644 index 0000000000..6411439143 --- /dev/null +++ b/libs/pigment/tests/TestKisSwatchGroup.cpp @@ -0,0 +1,103 @@ +#include "TestKisSwatchGroup.h" +#include + +void TestKisSwatchGroup::testAddingOneEntry() +{ + KisSwatch e; + e.setName("first"); + g.setEntry(e, 0, 0); + QVERIFY(g.checkEntry(0, 0)); + QVERIFY(!g.checkEntry(1, 2)); + QVERIFY(!g.checkEntry(10, 5)); + QCOMPARE(g.getEntry(0, 0), e); + QCOMPARE(g.colorCount(), 1); + testSwatches[QPair(0, 0)] = e; +} + +void TestKisSwatchGroup::testAddingMultipleEntries() +{ + KisSwatch e2; + e2.setName("second"); + g.setEntry(e2, 9, 3); + QCOMPARE(g.columnCount(), 16); + QVERIFY(g.checkEntry(9, 3)); + QVERIFY(!g.checkEntry(1, 2)); + QVERIFY(!g.checkEntry(10, 5)); + QVERIFY(g.checkEntry(0, 0)); + QCOMPARE(g.getEntry(0, 0).name(), QString("first")); + KisSwatch e3; + e3.setName("third"); + g.setEntry(e3, 4, 12); + QCOMPARE(g.colorCount(), 3); + QVERIFY(g.checkEntry(9, 3)); + QCOMPARE(g.getEntry(9, 3).name(), QString("second")); + testSwatches[QPair(9, 3)] = e2; + testSwatches[QPair(4, 12)] = e3; +} + +void TestKisSwatchGroup::testReplaceEntries() +{ + KisSwatch e4; + e4.setName("fourth"); + g.setEntry(e4, 0, 0); + QCOMPARE(g.colorCount(), 3); + QVERIFY(g.checkEntry(0, 0)); + QCOMPARE(g.getEntry(0, 0).name(), QString("fourth")); + testSwatches[QPair(0, 0)] = e4; +} + +void TestKisSwatchGroup::testRemoveEntries() +{ + testSwatches.remove(QPair(9, 3)); + QVERIFY(g.removeEntry(9, 3)); + QCOMPARE(g.colorCount(), testSwatches.size()); + QVERIFY(!g.removeEntry(13, 10)); + QVERIFY(!g.checkEntry(9, 3)); +} + +void TestKisSwatchGroup::testChangeColumnNumber() +{ + g.setColumnCount(20); + QCOMPARE(g.columnCount(), 20); + for (QPair p : testSwatches.keys()) { + QCOMPARE(testSwatches[p], g.getEntry(p.first, p.second)); + } + g.setColumnCount(10); + int keptCount = 0; + for (QPair p : testSwatches.keys()) { + if (p.first < 10) { + keptCount++; + QCOMPARE(testSwatches[p], g.getEntry(p.first, p.second)); + } + } + QCOMPARE(keptCount, g.colorCount()); +} + +void TestKisSwatchGroup::testAddEntry() +{ + KisSwatchGroup g2; + g2.setColumnCount(3); + g2.setRowCount(1); + for (int i = 0; i != 3; i++) { + g2.addEntry(KisSwatch()); + } + QCOMPARE(g2.rowCount(), 1); + QCOMPARE(g2.columnCount(), 3); + QCOMPARE(g2.colorCount(), 3); + g2.addEntry(KisSwatch()); + QCOMPARE(g2.rowCount(), 2); + QCOMPARE(g2.columnCount(), 3); + QCOMPARE(g2.colorCount(), 4); + g2.setRowCount(1); + QCOMPARE(g2.rowCount(), 1); + QCOMPARE(g2.columnCount(), 3); + QCOMPARE(g2.colorCount(), 3); + for (int i = 0; i != 4; i++) { + g2.addEntry(KisSwatch()); + } + QCOMPARE(g2.rowCount(), 3); + QCOMPARE(g2.columnCount(), 3); + QCOMPARE(g2.colorCount(), 7); +} + +QTEST_GUILESS_MAIN(TestKisSwatchGroup) diff --git a/libs/pigment/tests/TestKisSwatchGroup.h b/libs/pigment/tests/TestKisSwatchGroup.h new file mode 100644 index 0000000000..ef0f3dd24f --- /dev/null +++ b/libs/pigment/tests/TestKisSwatchGroup.h @@ -0,0 +1,25 @@ +#ifndef TESTKISSWATCHGROUP_H +#define TESTKISSWATCHGROUP_H + +#include +#include + +#include + +class TestKisSwatchGroup : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testAddingOneEntry(); + void testAddingMultipleEntries(); + void testReplaceEntries(); + void testRemoveEntries(); + void testChangeColumnNumber(); + void testAddEntry(); +private: + KisSwatchGroup g; + QHash, KisSwatch> testSwatches; +}; + + +#endif /* TESTKISSWATCHGROUP_H */ diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 37539bd2d6..7a1df5ca4a 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,590 +1,595 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ${EXIV2_INCLUDE_DIR} ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ) add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) find_library(APPKIT_LIBRARY AppKit) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp canvas/kis_paintop_transformation_connector.cpp canvas/kis_display_color_converter.cpp canvas/kis_display_filter.cpp canvas/kis_exposure_gamma_correction_interface.cpp canvas/kis_tool_proxy.cpp canvas/kis_canvas_decoration.cc canvas/kis_coordinates_converter.cpp canvas/kis_grid_manager.cpp canvas/kis_grid_decoration.cpp canvas/kis_grid_config.cpp canvas/kis_prescaled_projection.cpp canvas/kis_qpainter_canvas.cpp canvas/kis_projection_backend.cpp canvas/kis_update_info.cpp canvas/kis_image_patch.cpp canvas/kis_image_pyramid.cpp canvas/kis_infinity_manager.cpp canvas/kis_change_guides_command.cpp canvas/kis_guides_decoration.cpp canvas/kis_guides_manager.cpp canvas/kis_guides_config.cpp canvas/kis_snap_config.cpp canvas/kis_snap_line_strategy.cpp canvas/KisSnapPointStrategy.cpp dialogs/kis_about_application.cpp dialogs/kis_dlg_adj_layer_props.cc dialogs/kis_dlg_adjustment_layer.cc dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_generator_layer.cpp dialogs/kis_dlg_file_layer.cpp dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_stroke_selection_properties.cpp dialogs/kis_dlg_image_properties.cc dialogs/kis_dlg_layer_properties.cc dialogs/kis_dlg_preferences.cc dialogs/slider_and_spin_box_sync.cpp dialogs/kis_dlg_blacklist_cleanup.cpp dialogs/kis_dlg_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp dialogs/KisSessionManagerDialog.cpp dialogs/KisNewWindowLayoutDialog.cpp flake/kis_node_dummies_graph.cpp flake/kis_dummies_facade_base.cpp flake/kis_dummies_facade.cpp flake/kis_node_shapes_graph.cpp flake/kis_node_shape.cpp flake/kis_shape_controller.cpp flake/kis_shape_layer.cc flake/kis_shape_layer_canvas.cpp flake/kis_shape_selection.cpp flake/kis_shape_selection_canvas.cpp flake/kis_shape_selection_model.cpp flake/kis_take_all_shapes_command.cpp brushhud/kis_uniform_paintop_property_widget.cpp brushhud/kis_brush_hud.cpp brushhud/kis_round_hud_button.cpp brushhud/kis_dlg_brush_hud_config.cpp brushhud/kis_brush_hud_properties_list.cpp brushhud/kis_brush_hud_properties_config.cpp kis_aspect_ratio_locker.cpp kis_autogradient.cc kis_bookmarked_configurations_editor.cc kis_bookmarked_configurations_model.cc kis_bookmarked_filter_configurations_model.cc KisPaintopPropertiesBase.cpp kis_canvas_resource_provider.cpp kis_derived_resources.cpp kis_categories_mapper.cpp kis_categorized_list_model.cpp kis_categorized_item_delegate.cpp kis_clipboard.cc kis_config.cc kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_cursor.cc kis_cursor_cache.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_change_file_layer_command.h kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc kis_histogram_view.cc KisImageBarrierLockerWithFeedback.cpp kis_image_manager.cc kis_image_view_converter.cpp kis_import_catcher.cc kis_layer_manager.cc kis_mask_manager.cc kis_mimedata.cpp kis_node_commands_adapter.cpp kis_node_manager.cpp kis_node_juggler_compressed.cpp kis_node_selection_adapter.cpp kis_node_insertion_adapter.cpp KisNodeDisplayModeAdapter.cpp kis_node_model.cpp kis_node_filter_proxy_model.cpp kis_model_index_converter_base.cpp kis_model_index_converter.cpp kis_model_index_converter_show_all.cpp kis_painting_assistant.cc kis_painting_assistants_decoration.cpp KisDecorationsManager.cpp kis_paintop_box.cc kis_paintop_option.cpp kis_paintop_options_model.cpp kis_paintop_settings_widget.cpp kis_popup_palette.cpp kis_png_converter.cpp kis_preference_set_registry.cpp KisResourceServerProvider.cpp KisResourceBundleServerProvider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc KisSelectionActionsAdapter.cpp kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp KisActionPlugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp KisWelcomePageWidget.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp opengl/kis_opengl_shader_loader.cpp opengl/kis_texture_tile_info_pool.cpp opengl/KisOpenGLUpdateInfoBuilder.cpp kis_fps_decoration.cpp tool/KisToolChangesTracker.cpp tool/KisToolChangesTrackerData.cpp tool/kis_selection_tool_helper.cpp tool/kis_selection_tool_config_widget_helper.cpp tool/kis_rectangle_constraint_widget.cpp tool/kis_shape_tool_helper.cpp tool/kis_tool.cc tool/kis_delegated_tool_policies.cpp tool/kis_tool_freehand.cc tool/kis_speed_smoother.cpp tool/kis_painting_information_builder.cpp tool/kis_stabilized_events_sampler.cpp tool/kis_tool_freehand_helper.cpp tool/kis_tool_multihand_helper.cpp tool/kis_figure_painting_tool_helper.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/KisStabilizerDelayedPaintHelper.cpp tool/KisStrokeSpeedMonitor.cpp tool/strokes/freehand_stroke.cpp tool/strokes/KisStrokeEfficiencyMeasurer.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp tool/strokes/KisFreehandStrokeInfo.cpp tool/strokes/KisMaskedFreehandStrokePainter.cpp tool/strokes/KisMaskingBrushRenderer.cpp tool/strokes/KisMaskingBrushCompositeOpFactory.cpp tool/strokes/move_stroke_strategy.cpp widgets/kis_cmb_composite.cc widgets/kis_cmb_contour.cpp widgets/kis_cmb_gradient.cpp widgets/kis_paintop_list_widget.cpp widgets/kis_cmb_idlist.cc widgets/kis_color_space_selector.cc widgets/kis_advanced_color_space_selector.cc widgets/kis_cie_tongue_widget.cpp widgets/kis_tone_curve_widget.cpp widgets/kis_curve_widget.cpp widgets/kis_custom_image_widget.cc widgets/kis_image_from_clipboard_widget.cpp widgets/kis_double_widget.cc widgets/kis_filter_selector_widget.cc widgets/kis_gradient_chooser.cc widgets/kis_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_paintop_presets_save.cpp widgets/kis_paintop_preset_icon_library.cpp widgets/kis_pattern_chooser.cc widgets/kis_preset_chooser.cpp widgets/kis_progress_widget.cpp widgets/kis_selection_options.cc widgets/kis_scratch_pad.cpp widgets/kis_scratch_pad_event_filter.cpp widgets/kis_preset_selector_strip.cpp widgets/kis_slider_spin_box.cpp widgets/KisSelectionPropertySlider.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/kis_categorized_list_view.cpp widgets/kis_widget_chooser.cpp widgets/kis_tool_button.cpp widgets/kis_floating_message.cpp widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp widgets/kis_elided_label.cpp widgets/kis_stopgradient_slider_widget.cpp widgets/kis_preset_live_preview_view.cpp widgets/KisScreenColorPicker.cpp widgets/KoDualColorButton.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp + + KisPaletteEditor.cpp + dialogs/KisDlgPaletteEditor.cpp + widgets/KisNewsWidget.cpp widgets/KisGamutMaskToolbar.cpp utils/kis_document_aware_spin_box_unit_manager.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_native_gesture_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp input/KisQtWidgetsTweaker.cpp input/KisInputActionGroup.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactory.cpp 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 kis_asl_layer_style_serializer.cpp kis_psd_layer_style_resource.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisCloneDocumentStroke.cpp KisNodeDelegate.cpp kis_node_view_visibility_delegate.cpp KisNodeToolTip.cpp KisNodeView.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisFilterEntry.cpp KisImportExportManager.cpp KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoActionsUpdateManager.cpp KisView.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisResourceBundle.cpp KisResourceBundleManifest.cpp kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisSaveGroupVisitor.cpp KisWindowLayoutResource.cpp KisWindowLayoutManager.cpp KisSessionResource.cpp KisReferenceImagesDecoration.cpp KisReferenceImage.cpp flake/KisReferenceImagesLayer.cpp flake/KisReferenceImagesLayer.h ) if(WIN32) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp qtlockedfile/qtlockedfile_win.cpp input/wintab/kis_tablet_support_win8.cpp opengl/kis_opengl_win.cpp ) endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAsyncAnimationRendererBase.cpp KisAsyncAnimationCacheRenderer.cpp KisAsyncAnimationFramesSavingRenderer.cpp dialogs/KisAsyncAnimationRenderDialogBase.cpp dialogs/KisAsyncAnimationCacheRenderDialog.cpp dialogs/KisAsyncAnimationFramesSaveDialog.cpp canvas/kis_animation_player.cpp kis_animation_importer.cpp KisSyncedAudioPlayback.cpp KisFrameDataSerializer.cpp KisFrameCacheStore.cpp KisFrameCacheSwapper.cpp KisAbstractFrameCacheSwapper.cpp KisInMemoryFrameCacheSwapper.cpp input/wintab/drawpile_tablettester/tablettester.cpp input/wintab/drawpile_tablettester/tablettest.cpp ) if (UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_unix.cpp ) if(NOT USE_QT_XCB) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support.cpp ) endif() if(NOT APPLE AND NOT USE_QT_XCB) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/qxcbconnection_xi2.cpp input/wintab/qxcbconnection.cpp input/wintab/kis_xi2_event_filter.cpp ) endif() endif() if(APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} osx.mm ) endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgbookmarkedconfigurationseditor.ui forms/wdgapplyprofile.ui forms/wdgcustompattern.ui forms/wdglayerproperties.ui forms/wdgcolorsettings.ui forms/wdgtabletsettings.ui forms/wdgcolorspaceselector.ui forms/wdgcolorspaceselectoradvanced.ui forms/wdgdisplaysettings.ui forms/kis_previewwidgetbase.ui forms/kis_matrix_widget.ui forms/wdgselectionoptions.ui forms/wdggeometryoptions.ui forms/wdgnewimage.ui forms/wdgimageproperties.ui forms/wdgmaskfromselection.ui forms/wdgmasksource.ui forms/wdgfilterdialog.ui forms/wdgmetadatamergestrategychooser.ui forms/wdgpaintoppresets.ui forms/wdgpaintopsettings.ui forms/wdgdlggeneratorlayer.ui forms/wdgdlgfilelayer.ui forms/wdgfilterselector.ui forms/wdgfilternodecreation.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgsavebrushpreset.ui forms/wdgpreseticonlibrary.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui forms/wdgsessionmanager.ui forms/wdgnewwindowlayout.ui forms/KisWelcomePage.ui + forms/WdgDlgPaletteEditor.ui forms/KisNewsPage.ui forms/wdgGamutMaskToolbar.ui brushhud/kis_dlg_brush_hud_config.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui input/wintab/drawpile_tablettester/tablettest.ui ) QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h) add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES} ) if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (HAVE_KIO) target_link_libraries(kritaui KF5::KIOCore) endif() if (NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB} ${XCB_LIBRARIES}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) target_link_libraries(kritaui ${APPKIT_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras) endif() if(X11_FOUND) target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES}) endif() target_include_directories(kritaui PUBLIC $ $ $ $ $ $ $ ) set_target_properties(kritaui PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS}) if (APPLE) install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita) endif () diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 2e94e94e89..cd98d4d4f7 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1844 +1,1857 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include "KisReferenceImagesLayer.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" #include "KisCloneDocumentStroke.h" // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *q) : docInfo(new KoDocumentInfo(q)) // deleted by QObject , importExportManager(new KisImportExportManager(q)) // deleted manually , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) // deleted by QObject , m_bAutoDetectedMime(false) , modified(false) , readwrite(true) , firstMod(QDateTime::currentDateTime()) , lastMod(firstMod) , nserver(new KisNameServer(1)) , imageIdleWatcher(2000 /*ms*/) , globalAssistantsColor(KisConfig(true).defaultAssistantsColor()) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *q) : docInfo(new KoDocumentInfo(*rhs.docInfo, q)) , unit(rhs.unit) , importExportManager(new KisImportExportManager(q)) , mimeType(rhs.mimeType) , outputMimeType(rhs.outputMimeType) , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) , guidesConfig(rhs.guidesConfig) , m_bAutoDetectedMime(rhs.m_bAutoDetectedMime) , m_url(rhs.m_url) , m_file(rhs.m_file) , modified(rhs.modified) , readwrite(rhs.readwrite) , firstMod(rhs.firstMod) , lastMod(rhs.lastMod) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document! , globalAssistantsColor(rhs.globalAssistantsColor) + , paletteList(rhs.paletteList) , gridConfig(rhs.gridConfig) , savingLock(&savingMutex) , batchMode(rhs.batchMode) { // TODO: clone assistants } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; QColor globalAssistantsColor; KisSharedPtr referenceImagesLayer; + QList paletteList; + KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class StrippedSafeSavingLocker; }; class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(rhs.objectName()); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(true), false); if (rhs.d->preActivatedNode) { // since we clone uuid's, we can use them for lacating new // nodes. Otherwise we would need to use findSymmetricClone() d->preActivatedNode = KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid()); } } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer->disconnect(this); d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { slotCompleteSavingDocument(job, KisImportExportFilter::CreationError, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); return false; } KisConfig cfg(true); if (cfg.backupFile() && filePathInfo.exists()) { KBackup::backupFile(job.filePath); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), job, exportConfiguration); if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; return exportDocumentImpl(ExportFileJob(url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { if (status == KisImportExportFilter::UserCancelled) return; const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage))); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { const QString existingAutoSaveBaseName = localFilePath(); const bool wasRecovered = isRecovered(); setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { /** * If undo stack is alreado clean/empty, it doesn't emit any * signals, so we might forget update document modified state * (which was set, e.g. while recovering an autosave file) */ if (d->undoStack->isClean()) { setModified(false); } else { d->undoStack->setClean(); } } setRecovered(false); removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->batchMode; } void KisDocument::setFileBatchMode(const bool batchMode) { d->batchMode = batchMode; } KisDocument* KisDocument::lockAndCloneForSaving() { // force update of all the asynchronous nodes before cloning QApplication::processEvents(); KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root()); KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { if (!window->viewManager()->blockUntilOperationsFinished(d->image)) { return 0; } } } Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportFilter::ConversionStatus status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status == KisImportExportFilter::OK; } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { return initiateSavingInBackground(actionName, receiverObject, receiverMethod, job, exportConfiguration, std::unique_ptr()); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument; if (!optionalClonedDocument) { clonedDocument.reset(lockAndCloneForSaving()); } else { clonedDocument.reset(optionalClonedDocument.release()); } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus,QString)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus,QString))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { // the state should have been deinitialized in slotChildCompletedSavingInBackground() KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } } return started; } void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) { d->savingMutex.unlock(); return; } KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument); if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid()); const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0, std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), Qt::BlockingQueuedConnection); KisStrokeId strokeId = d->image->startStroke(stroke); d->image->endStroke(strokeId); setInfiniteAutoSaveInterval(); } else if (!started) { setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotAutoSave() { slotAutoSaveImpl(std::unique_ptr()); } void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) { slotAutoSaveImpl(std::unique_ptr(clonedDocument)); } void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (status != KisImportExportFilter::OK) { setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg(true); d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer->stop(); // until the next change d->autoSaveFailureCount = 0; } else { setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } KisImportExportFilter::ConversionStatus initializationStatus; d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage()); return false; } typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, ""); return; } KisImportExportFilter::ConversionStatus status = d->childSavingFuture.result(); const QString errorMessage = this->errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { if (isReadWrite() && delay > 0) { d->autoSaveTimer->start(delay * 1000); } else { d->autoSaveTimer->stop(); } } void KisDocument::setNormalAutoSaveInterval() { setAutoSaveDelay(d->autoSaveDelay); d->autoSaveFailureCount = 0; } void KisDocument::setEmergencyAutoSaveInterval() { const int emergencyAutoSaveInterval = 10; /* sec */ setAutoSaveDelay(emergencyAutoSaveInterval); d->autoSaveFailureCount++; } void KisDocument::setInfiniteAutoSaveInterval() { setAutoSaveDelay(-1); } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QRegularExpression autosavePattern("^\\..+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern.match(filename).hasMatch()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension); #endif } else { retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } if (ret) { // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

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

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window && window->viewManager()) { KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } KisImportExportFilter::ConversionStatus status; status = d->importExportManager->importDocument(localFilePath(), typeName); if (status != KisImportExportFilter::OK) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); emit sigLoadingFinished(); undoStack()->clear(); return true; } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; const QString _url(url().fileName()); // if URL is empty...it is probably an unsaved file if (_url.isEmpty()) { c = " [" + i18n("Not Saved") + "] "; } else { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered) { //qDebug() << "removeAutoSaveFiles"; // Eliminate any auto-save file QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir //qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf); if (QFile::exists(asf)) { //qDebug() << "\tremoving autosavefile" << asf; QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME //qDebug() << "Autsavefile in $home" << asf; if (QFile::exists(asf)) { //qDebug() << "\tremoving autsavefile 2" << asf; QFile::remove(asf); } QRegularExpression autosavePattern("^\\..+-autosave.kra$"); if (wasRecovered && !autosaveBaseName.isEmpty() && autosavePattern.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() && QFile::exists(autosaveBaseName)) { QFile::remove(autosaveBaseName); } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg(true); d->undoStack->setUndoLimit(cfg.undoStackLimit()); d->autoSaveDelay = cfg.autoSaveInterval(); setNormalAutoSaveInterval(); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { d->gridConfig = config; } +QList &KisDocument::paletteList() +{ + return d->paletteList; +} + +void KisDocument::setPaletteList(const QList &paletteList) +{ + d->paletteList = paletteList; +} + const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) return false; if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) return false; d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisImageSP image; KisPaintLayerSP layer; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); documentInfo()->setAboutInfo("title", name); documentInfo()->setAboutInfo("abstract", description); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); Q_CHECK_PTR(layer); if (backgroundAsLayer) { image->setDefaultProjectionColor(KoColor(cs)); if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) { layer->paintDevice()->setDefaultPixel(bgColor); } else { // Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel KisFillPainter painter; painter.begin(layer->paintDevice()); painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8()); } } else { image->setDefaultProjectionColor(bgColor); } layer->setDirty(QRect(0, 0, width, height)); image->addNode(layer.data(), image->rootLayer().data()); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } KisConfig cfg(false); cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); QApplication::restoreOverrideCursor(); return true; } bool KisDocument::isSaving() const { const bool result = d->savingMutex.tryLock(); if (result) { d->savingMutex.unlock(); } return !result; } void KisDocument::waitForSavingToComplete() { if (isSaving()) { KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); f.waitForMutex(&d->savingMutex); } } KoShapeControllerBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList &value) { d->assistants = value; } KisSharedPtr KisDocument::referenceImagesLayer() const { return d->referenceImagesLayer.data(); } void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage) { if (d->referenceImagesLayer) { d->referenceImagesLayer->disconnect(this); } if (updateImage) { if (layer) { d->image->addNode(layer); } else { d->image->removeNode(d->referenceImagesLayer); } } d->referenceImagesLayer = layer; if (d->referenceImagesLayer) { connect(d->referenceImagesLayer, SIGNAL(sigUpdateCanvas(QRectF)), this, SIGNAL(sigReferenceImagesChanged())); } } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate) { if (d->image) { // Disconnect existing sig/slot connections d->image->setUndoStore(new KisDumbUndoStore()); d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; 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); if (forceInitialUpdate) { d->image->initialRefreshGraph(); } } void KisDocument::hackPreliminarySetImage(KisImageSP image) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image); d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); } void KisDocument::setImageModified() { setModified(true); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) { return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage; } void KisDocument::setAssistantsGlobalColor(QColor color) { d->globalAssistantsColor = color; } QColor KisDocument::assistantsGlobalColor() { return d->globalAssistantsColor; } QRectF KisDocument::documentBounds() const { QRectF bounds = d->image->bounds(); if (d->referenceImagesLayer) { bounds |= d->referenceImagesLayer->boundingImageRect(); } return bounds; } diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h index f463058cdd..11f80ba458 100644 --- a/libs/ui/KisDocument.h +++ b/libs/ui/KisDocument.h @@ -1,651 +1,655 @@ /* 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 "kritaui_export.h" #include class QString; class KUndo2Command; class KoUnit; class KoColor; class KoColorSpace; class KoShapeControllerBase; class KoShapeLayer; class KoStore; class KoOdfReadStore; class KoDocumentInfo; class KoDocumentInfoDlg; class KisImportExportManager; class KisUndoStore; class KisPart; class KisGridConfig; class KisGuidesConfig; class QDomDocument; class KisReferenceImagesLayer; #define KIS_MIME_TYPE "application/x-krita" /** * The %Calligra document class * * This class provides some functionality each %Calligra document should have. * * @short The %Calligra document class */ class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase { Q_OBJECT protected: explicit KisDocument(); /** * @brief KisDocument makes a deep copy of the document \p rhs. * The caller *must* ensure that the image is properly * locked and is in consistent state before asking for * cloning. * @param rhs the source document to copy from */ explicit KisDocument(const KisDocument &rhs); public: enum OpenFlag { None = 0, DontAddToRecent = 0x1, RecoveryFile = 0x2 }; Q_DECLARE_FLAGS(OpenFlags, OpenFlag) /** * Destructor. * * The destructor does not delete any attached KisView objects and it does not * delete the attached widget as returned by widget(). */ ~KisDocument() override; /** * @brief reload Reloads the document from the original url * @return the result of loading the document */ bool reload(); /** * @brief creates a clone of the document and returns it. Please make sure that you * hold all the necessary locks on the image before asking for a clone! */ KisDocument* clone(); /** * @brief openUrl Open an URL * @param url The URL to open * @param flags Control specific behavior * @return success status */ bool openUrl(const QUrl &url, OpenFlags flags = None); /** * Opens the document given by @p url, without storing the URL * in the KisDocument. * Call this instead of openUrl() to implement KisMainWindow's * File --> Import feature. * * @note This will call openUrl(). To differentiate this from an ordinary * Open operation (in any reimplementation of openUrl() or openFile()) * call isImporting(). */ bool importDocument(const QUrl &url); /** * Saves the document as @p url without changing the state of the * KisDocument (URL, modified flag etc.). Call this instead of * KisParts::ReadWritePart::saveAs() to implement KisMainWindow's * File --> Export feature. */ bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0); bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0); private: bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); public: /** * @brief Sets whether the document can be edited or is read only. * * This recursively applied to all child documents and * KisView::updateReadWrite is called for every attached * view. */ void setReadWrite(bool readwrite = true); /** * To be preferred when a document exists. It is fast when calling * it multiple times since it caches the result that readNativeFormatMimeType() * delivers. * This comes from the X-KDE-NativeMimeType key in the .desktop file. */ static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; } /// Checks whether a given mimetype can be handled natively. bool isNativeFormat(const QByteArray& mimetype) const; /// Returns a list of the mimetypes considered "native", i.e. which can /// be saved by KisDocument without a filter, in *addition* to the main one static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; } /** * Returns the actual mimetype of the document */ QByteArray mimeType() const override; /** * @brief Sets the mime type for the document. * * When choosing "save as" this is also the mime type * selected by default. */ void setMimeType(const QByteArray & mimeType) override; /** * @return true if file operations should inhibit the option dialog */ bool fileBatchMode() const; /** * @param batchMode if true, do not show the option dialog for file operations. */ void setFileBatchMode(const bool batchMode); /** * Sets the error message to be shown to the user (use i18n()!) * when loading or saving fails. * If you asked the user about something and they chose "Cancel", */ void setErrorMessage(const QString& errMsg); /** * Return the last error message. Usually KisDocument takes care of * showing it; this method is mostly provided for non-interactive use. */ QString errorMessage() const; /** * Sets the warning message to be shown to the user (use i18n()!) * when loading or saving fails. */ void setWarningMessage(const QString& warningMsg); /** * Return the last warning message set by loading or saving. Warnings * mean that the document could not be completely loaded, but the errors * were not absolutely fatal. */ QString warningMessage() const; /** * @brief Generates a preview picture of the document * @note The preview is used in the File Dialog and also to create the Thumbnail */ QPixmap generatePreview(const QSize& size); /** * Tells the document that its title has been modified, either because * the modified status changes (this is done by setModified() ) or * because the URL or the document-info's title changed. */ void setTitleModified(); /** * @brief Sets the document to empty. * * Used after loading a template * (which is not empty, but not the user's input). * * @see isEmpty() */ void setEmpty(bool empty = true); /** * Return a correctly created QDomDocument for this KisDocument, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * @param tagName the name of the tag for the root element * @param version the DTD version (usually the application's version). */ QDomDocument createDomDocument(const QString& tagName, const QString& version) const; /** * Return a correctly created QDomDocument for an old (1.3-style) %Calligra document, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * This static method can be used e.g. by filters. * @param appName the app's instance name, e.g. words, kspread, kpresenter etc. * @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter. * @param version the DTD version (usually the application's version). */ static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version); /** * Loads a document in the native format from a given URL. * Reimplement if your native format isn't XML. * * @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter */ bool loadNativeFormat(const QString & file); /** * Set standard autosave interval that is set by a config file */ void setNormalAutoSaveInterval(); /** * Set emergency interval that autosave uses when the image is busy, * by default it is 10 sec */ void setEmergencyAutoSaveInterval(); /** * Disable autosave */ void setInfiniteAutoSaveInterval(); /** * @return the information concerning this document. * @see KoDocumentInfo */ KoDocumentInfo *documentInfo() const; /** * Performs a cleanup of unneeded backup files */ void removeAutoSaveFiles(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); + QList &paletteList(); + void setPaletteList(const QList &paletteList); + void clearUndoHistory(); /** * Sets the modified flag on the document. This means that it has * to be saved or not before deleting it. */ void setModified(bool _mod); void setRecovered(bool value); bool isRecovered() const; void updateEditingTime(bool forceStoreElapsed); /** * Returns the global undo stack */ KUndo2Stack *undoStack(); /** * @brief importExportManager gives access to the internal import/export manager * @return the document's import/export manager */ KisImportExportManager *importExportManager() const; /** * @brief serializeToNativeByteArray daves the document into a .kra file wtitten * to a memory-based byte-array * @return a byte array containing the .kra file */ QByteArray serializeToNativeByteArray(); /** * @brief isInSaving shown if the document has any (background) saving process or not * @return true if there is some saving in action */ bool isInSaving() const; public Q_SLOTS: /** * Adds a command to the undo stack and executes it by calling the redo() function. * @param command command to add to the undo stack */ void addCommand(KUndo2Command *command); /** * Begins recording of a macro command. At the end endMacro needs to be called. * @param text command description */ void beginMacro(const KUndo2MagicString &text); /** * Ends the recording of a macro command. */ void endMacro(); Q_SIGNALS: /** * This signal is emitted when the unit is changed by setUnit(). * It is common to connect views to it, in order to change the displayed units * (e.g. in the rulers) */ void unitChanged(const KoUnit &unit); /** * Emitted e.g. at the beginning of a save operation * This is emitted by KisDocument and used by KisView to display a statusbar message */ void statusBarMessage(const QString& text, int timeout = 0); /** * Emitted e.g. at the end of a save operation * This is emitted by KisDocument and used by KisView to clear the statusbar message */ void clearStatusBarMessage(); /** * Emitted when the document is modified */ void modified(bool); void titleModified(const QString &caption, bool isModified); void sigLoadingFinished(); void sigSavingFinished(); void sigGuidesConfigChanged(const KisGuidesConfig &config); void sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void sigReferenceImagesChanged(); private Q_SLOTS: void finishExportInBackground(); void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void slotInitiateAsyncAutosaving(KisDocument *clonedDocument); private: friend class KisPart; friend class SafeSavingLocker; bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument); bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); bool startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Activate/deactivate/configure the autosave feature. * @param delay in seconds, 0 to disable */ void setAutoSaveDelay(int delay); /** * Generate a name for the document. */ QString newObjectName(); QString generateAutoSaveFileName(const QString & path) const; /** * Loads a document * * Applies a filter if necessary, and calls loadNativeFormat in any case * You should not have to reimplement, except for very special cases. * * NOTE: this method also creates a new KisView instance! * * This method is called from the KReadOnlyPart::openUrl method. */ bool openFile(); public: bool isAutosaving() const override; public: QString localFilePath() const override; void setLocalFilePath( const QString &localFilePath ); KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const; bool isReadWrite() const; QUrl url() const override; void setUrl(const QUrl &url) override; bool closeUrl(bool promptToSave = true); bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0); /** * Create a new image that has this document as a parent and * replace the current image with this image. */ bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &imageDescription, const double imageResolution); bool isSaving() const; void waitForSavingToComplete(); KisImageWSP image() const; /** * @brief savingImage provides a detached, shallow copy of the original image that must be used when saving. * Any strokes in progress will not be applied to this image, so the result might be missing some data. On * the other hand, it won't block. * * @return a shallow copy of the original image, or 0 is saving is not in progress */ KisImageSP savingImage() const; /** * Set the current image to the specified image and turn undo on. */ void setCurrentImage(KisImageSP image, bool forceInitialUpdate = true); /** * Set the image of the document preliminary, before the document * has completed loading. Some of the document items (shapes) may want * to access image properties (bounds and resolution), so we should provide * it to them even before the entire image is loaded. * * Right now, the only use by KoShapeRegistry::createShapeFromOdf(), remove * after it is deprecated. */ void hackPreliminarySetImage(KisImageSP image); KisUndoStore* createUndoStore(); /** * The shape controller matches internal krita image layers with * the flake shape hierarchy. */ KoShapeControllerBase * shapeController() const; KoShapeLayer* shapeForNode(KisNodeSP layer) const; /** * Set the list of nodes that was marked as currently active. Used *only* * for saving loading. Never use it for tools or processing. */ void setPreActivatedNode(KisNodeSP activatedNode); /** * @return the node that was set as active during loading. Used *only* * for saving loading. Never use it for tools or processing. */ KisNodeSP preActivatedNode() const; /// @return the list of assistants associated with this document QList assistants() const; /// @replace the current list of assistants with @param value void setAssistants(const QList &value); void setAssistantsGlobalColor(QColor color); QColor assistantsGlobalColor(); /** * Get existing reference images layer or null if none exists. */ KisSharedPtr referenceImagesLayer() const; void setReferenceImagesLayer(KisSharedPtr layer, bool updateImage); bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Return the bounding box of the image and associated elements (e.g. reference images) */ QRectF documentBounds() const; Q_SIGNALS: void completed(); void canceled(const QString &); private Q_SLOTS: void setImageModified(); void slotAutoSave(); void slotUndoStackCleanChanged(bool value); void slotConfigChanged(); private: /** * @brief try to clone the image. This method handles all the locking for you. If locking * has failed, no cloning happens * @return cloned document on success, null otherwise */ KisDocument *lockAndCloneForSaving(); QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); QString prettyPathOrUrl() const; bool openUrlInternal(const QUrl &url); void slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument); class Private; Private *const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags) Q_DECLARE_METATYPE(KisDocument*) #endif diff --git a/libs/ui/KisPaletteEditor.cpp b/libs/ui/KisPaletteEditor.cpp new file mode 100644 index 0000000000..e1008b5769 --- /dev/null +++ b/libs/ui/KisPaletteEditor.cpp @@ -0,0 +1,664 @@ +/* + * 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 + +#include "KisPaletteEditor.h" + +struct KisPaletteEditor::PaletteInfo { + QString name; + QString filename; + int columnCount; + bool isGlobal; + bool isReadOnly; + 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; + + 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; } + 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); + if (dlg.exec() != QDialog::Accepted) { return; } + KoColorSet *newColorSet = new KoColorSet(newPaletteFileName(false)); + newColorSet->setPaletteType(KoColorSet::KPL); + newColorSet->setIsGlobal(false); + newColorSet->setIsEditable(true); + newColorSet->setValid(true); + newColorSet->setName(le.text()); + m_d->rServer->addResource(newColorSet); + m_d->rServer->removeFromBlacklist(newColorSet); + + uploadPaletteList(); +} + +void KisPaletteEditor::importPalette() +{ + KoFileDialog dialog(Q_NULLPTR, 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; + } + KoColorSet *colorSet = new KoColorSet(filename); + colorSet->load(); + QString name = filenameFromPath(colorSet->filename()); + if (duplicateExistsFilename(name, false)) { + colorSet->setFilename(newPaletteFileName(false)); + } else { + colorSet->setFilename(name); + } + colorSet->setIsGlobal(false); + m_d->rServer->addResource(colorSet); + m_d->rServer->removeFromBlacklist(colorSet); + + uploadPaletteList(); +} + +void KisPaletteEditor::removePalette(KoColorSet *cs) +{ + if (!m_d->view) { return; } + if (!m_d->view->document()) { return; } + if (!cs || !cs->isEditable()) { + return; + } + + if (cs->isGlobal()) { + m_d->rServer->removeResourceAndBlacklist(cs); + QFile::remove(cs->filename()); + 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->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; + } +} + +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"), 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->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; } + KoColorSet *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); + } + 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(); + } + } + 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(); + } +} + +void KisPaletteEditor::slotPaletteChanged() +{ + Q_ASSERT(m_d->model); + if (!m_d->model->colorSet()) { return; } + KoColorSet *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(); + 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; } + + KoColorSet *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 KoResource *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; } + KoColorSet *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) +{ + QSet nameSet; + + Q_FOREACH (const KoResource *r, m_d->rServer->resources()) { + nameSet.insert(r->filename()); + } + + KoColorSet tmpColorSet; + QString result = "new_palette_"; + + if (isGlobal) { + result = m_d->rServer->saveLocation() + result; + } + + int i = 0; + while (nameSet.contains(result + QString::number(i) + tmpColorSet.defaultFileExtension())) { + i++; + } + result = result + 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 (KoResource * paletteResource, m_d->rServer->resources()) { + KoColorSet *palette = static_cast(paletteResource); + 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 new file mode 100644 index 0000000000..abda68e712 --- /dev/null +++ b/libs/ui/KisPaletteEditor.h @@ -0,0 +1,148 @@ +/* + * 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 = Q_NULLPTR); + ~KisPaletteEditor(); + + void setPaletteModel(KisPaletteModel *model); + void setView(KisViewManager *view); + + void addPalette(); + void importPalette(); + void removePalette(KoColorSet *); + + /** + * @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 name + * @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 + * @param name original group name + * @param rowNumber + * @return new group's name if change accpeted, 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 accpeted, 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); + 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/dialogs/KisDlgPaletteEditor.cpp b/libs/ui/dialogs/KisDlgPaletteEditor.cpp new file mode 100644 index 0000000000..7d7cf09f6f --- /dev/null +++ b/libs/ui/dialogs/KisDlgPaletteEditor.cpp @@ -0,0 +1,235 @@ +/* + * 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()) { + 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 new file mode 100644 index 0000000000..dedd193f08 --- /dev/null +++ b/libs/ui/dialogs/KisDlgPaletteEditor.h @@ -0,0 +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 "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 *); + KoColorSet* 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; + QPointer m_colorSet; + QString m_currentGroupOriginalName; + + QPalette m_normalPalette; + QPalette m_warnPalette; +}; + +#endif // KISKisDlgPaletteEditor_H diff --git a/libs/ui/forms/WdgDlgPaletteEditor.ui b/libs/ui/forms/WdgDlgPaletteEditor.ui new file mode 100644 index 0000000000..f22c584c6b --- /dev/null +++ b/libs/ui/forms/WdgDlgPaletteEditor.ui @@ -0,0 +1,217 @@ + + + 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/widgets/KisScreenColorPicker.cpp b/libs/ui/widgets/KisScreenColorPicker.cpp index 90b6dbe817..f69820cb2d 100644 --- a/libs/ui/widgets/KisScreenColorPicker.cpp +++ b/libs/ui/widgets/KisScreenColorPicker.cpp @@ -1,274 +1,275 @@ /* * 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 #include #include #include "kis_shared_ptr.h" #include "kis_icon.h" #include "kis_image.h" #include "kis_wrapped_rect.h" #include "KisPart.h" #include "KisScreenColorPicker.h" #include "KisDlgInternalColorSelector.h" struct KisScreenColorPicker::Private { QPushButton *screenColorPickerButton = 0; QLabel *lblScreenColorInfo = 0; KoColor currentColor = KoColor(); KoColor beforeScreenColorPicking = KoColor(); KisScreenColorPickingEventFilter *colorPickingEventFilter = 0; #ifdef Q_OS_WIN32 QTimer *updateTimer = 0; QWindow dummyTransparentWindow; #endif }; KisScreenColorPicker::KisScreenColorPicker(QWidget *parent) : KisScreenColorPickerBase(parent), m_d(new Private) { QVBoxLayout *layout = new QVBoxLayout(); this->setLayout(layout); m_d->screenColorPickerButton = new QPushButton(); m_d->screenColorPickerButton->setMinimumHeight(25); this->layout()->addWidget(m_d->screenColorPickerButton); m_d->lblScreenColorInfo = new QLabel(QLatin1String("\n")); this->layout()->addWidget(m_d->lblScreenColorInfo); connect(m_d->screenColorPickerButton, SIGNAL(clicked()), SLOT(pickScreenColor())); updateIcons(); #ifdef Q_OS_WIN32 m_d->updateTimer = new QTimer(this); m_d->dummyTransparentWindow.resize(1, 1); m_d->dummyTransparentWindow.setFlags(Qt::Tool | Qt::FramelessWindowHint); connect(m_d->updateTimer, SIGNAL(timeout()), SLOT(updateColorPicking())); #endif } KisScreenColorPicker::~KisScreenColorPicker() { } void KisScreenColorPicker::updateIcons() { m_d->screenColorPickerButton->setIcon(kisIcon("krita_tool_color_picker")); } KoColor KisScreenColorPicker::currentColor() { return m_d->currentColor; } void KisScreenColorPicker::pickScreenColor() { if (!m_d->colorPickingEventFilter) m_d->colorPickingEventFilter = new KisScreenColorPickingEventFilter(this); this->installEventFilter(m_d->colorPickingEventFilter); // If user pushes Escape, the last color before picking will be restored. m_d->beforeScreenColorPicking = currentColor(); grabMouse(Qt::CrossCursor); #ifdef Q_OS_WIN32 // excludes WinCE and WinRT // On Windows mouse tracking doesn't work over other processes's windows m_d->updateTimer->start(30); // HACK: Because mouse grabbing doesn't work across processes, we have to have a dummy, // invisible window to catch the mouse click, otherwise we will click whatever we clicked // and loose focus. m_d->dummyTransparentWindow.show(); #endif grabKeyboard(); /* With setMouseTracking(true) the desired color can be more precisely picked up, * and continuously pushing the mouse button is not necessary. */ setMouseTracking(true); //emit to the rest of the dialog to disable. Q_EMIT sigPleaseDisableEverything(true); m_d->screenColorPickerButton->setDisabled(true); const QPoint globalPos = QCursor::pos(); setCurrentColor(grabScreenColor(globalPos)); updateColorLabelText(globalPos); } void KisScreenColorPicker::setCurrentColor(KoColor c) { m_d->currentColor = c; } KoColor KisScreenColorPicker::grabScreenColor(const QPoint &p) { // First check whether we're clicking on a Krita window for some real color picking Q_FOREACH(KisView *view, KisPart::instance()->views()) { QWidget *canvasWidget = view->canvasBase()->canvasWidget(); QPoint widgetPoint = canvasWidget->mapFromGlobal(p); if (canvasWidget->rect().contains(widgetPoint)) { QPointF imagePoint = view->canvasBase()->coordinatesConverter()->widgetToImage(widgetPoint); KisImageWSP image = view->image(); if (image) { if (image->wrapAroundModePermitted()) { imagePoint = KisWrappedRect::ptToWrappedPt(imagePoint.toPoint(), image->bounds()); } KoColor pickedColor = KoColor(); image->projection()->pixel(imagePoint.x(), imagePoint.y(), &pickedColor); return pickedColor; } } } // And otherwise, we'll check the desktop const QDesktopWidget *desktop = QApplication::desktop(); const QPixmap pixmap = QGuiApplication::screens().at(desktop->screenNumber())->grabWindow(desktop->winId(), p.x(), p.y(), 1, 1); QImage i = pixmap.toImage(); KoColor col = KoColor(); col.fromQColor(QColor::fromRgb(i.pixel(0, 0))); return col; } void KisScreenColorPicker::updateColorLabelText(const QPoint &globalPos) { KoColor col = grabScreenColor(globalPos); QString colname = KoColor::toQString(col); QString location = QString::number(globalPos.x())+QString(", ")+QString::number(globalPos.y()); m_d->lblScreenColorInfo->setWordWrap(true); m_d->lblScreenColorInfo->setText(location+QString(": ")+colname); } bool KisScreenColorPicker::handleColorPickingMouseMove(QMouseEvent *e) { // If the cross is visible the grabbed color will be black most of the times //cp->setCrossVisible(!cp->geometry().contains(e->pos())); continueUpdateColorPicking(e->globalPos()); return true; } bool KisScreenColorPicker::handleColorPickingMouseButtonRelease(QMouseEvent *e) { setCurrentColor(grabScreenColor(e->globalPos())); Q_EMIT sigNewColorPicked(currentColor()); releaseColorPicking(); return true; } bool KisScreenColorPicker::handleColorPickingKeyPress(QKeyEvent *e) { #if QT_VERSION >= 0x050600 if (e->matches(QKeySequence::Cancel)) { #else if (e->key() == Qt::Key_Escape) { #endif releaseColorPicking(); setCurrentColor(m_d->beforeScreenColorPicking); } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { setCurrentColor(grabScreenColor(QCursor::pos())); releaseColorPicking(); } e->accept(); return true; } void KisScreenColorPicker::releaseColorPicking() { removeEventFilter(m_d->colorPickingEventFilter); releaseMouse(); #ifdef Q_OS_WIN32 m_d->updateTimer->stop(); m_d->dummyTransparentWindow.setVisible(false); #endif releaseKeyboard(); setMouseTracking(false); m_d->lblScreenColorInfo->setText(QLatin1String("\n")); //emit enable signal Q_EMIT sigPleaseDisableEverything(false); m_d->screenColorPickerButton->setDisabled(false); } void KisScreenColorPicker::changeEvent(QEvent *e) { QWidget::changeEvent(e); } void KisScreenColorPicker::updateColorPicking() { static QPoint lastGlobalPos; QPoint newGlobalPos = QCursor::pos(); if (lastGlobalPos == newGlobalPos) return; lastGlobalPos = newGlobalPos; if (!rect().contains(mapFromGlobal(newGlobalPos))) { // Inside the dialog mouse tracking works, handleColorPickingMouseMove will be called continueUpdateColorPicking(newGlobalPos); #ifdef Q_OS_WIN32 m_d->dummyTransparentWindow.setPosition(newGlobalPos); #endif } } void KisScreenColorPicker::continueUpdateColorPicking(const QPoint &globalPos) { const KoColor color = grabScreenColor(globalPos); // QTBUG-39792, do not change standard, custom color selectors while moving as // otherwise it is not possible to pre-select a custom cell for assignment. setCurrentColor(color); updateColorLabelText(globalPos); } // Event filter to be installed on the dialog while in color-picking mode. KisScreenColorPickingEventFilter::KisScreenColorPickingEventFilter(KisScreenColorPicker *w, QObject *parent) : QObject(parent) , m_w(w) {} bool KisScreenColorPickingEventFilter::eventFilter(QObject *, QEvent *event) { switch (event->type()) { case QEvent::MouseMove: return m_w->handleColorPickingMouseMove(static_cast(event)); case QEvent::MouseButtonRelease: return m_w->handleColorPickingMouseButtonRelease(static_cast(event)); case QEvent::KeyPress: return m_w->handleColorPickingKeyPress(static_cast(event)); default: break; } return false; } +std::function KisDlgInternalColorSelector::s_screenColorPickerFactory = KisScreenColorPicker::createScreenColorPicker; diff --git a/libs/widgets/CMakeLists.txt b/libs/widgets/CMakeLists.txt index c39c5ab054..bc6ed99758 100644 --- a/libs/widgets/CMakeLists.txt +++ b/libs/widgets/CMakeLists.txt @@ -1,143 +1,142 @@ add_subdirectory( tests ) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(kritawidgets_LIB_SRCS KoGradientEditWidget.cpp KoVBox.cpp KoDialog.cpp KoZoomWidget.cpp KoTagToolButton.cpp KoTagChooserWidget.cpp KoTagFilterWidget.cpp KoResourceTaggingManager.cpp KoResourceItemChooserContextMenu.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 - KoEditColorSetDialog.cpp KoTriangleColorSelector.cpp KoResourcePopupAction.cpp KoIconToolTip.cpp KoResourceItemChooser.cpp KoResourceItemChooserSync.cpp KoResourceSelector.cpp KoResourceModel.cpp KoResourceItemDelegate.cpp KoResourceItemView.cpp KoResourceTagStore.cpp KoRuler.cpp KoItemToolTip.cpp KoCheckerBoardPainter.cpp KoResourceServerAdapter.cpp KoResourceServerProvider.cpp KoLineStyleSelector.cpp KoLineStyleItemDelegate.cpp KoLineStyleModel.cpp KoResourceFiltering.cpp KoTitledTabWidget.cpp KoToolBoxButton.cpp KoToolBox.cpp KoToolBoxDocker.cpp KoToolBoxFactory.cpp KoToolDocker.cpp - KisPaletteModel.cpp - kis_palette_delegate.cpp - kis_palette_view.cpp - KoPageLayoutWidget.cpp KoPageLayoutDialog.cpp KoShadowConfigWidget.cpp KoMarkerSelector.cpp KoMarkerModel.cpp KoMarkerItemDelegate.cpp KoDocumentInfoDlg.cpp KoTableView.cpp WidgetsDebug.cpp kis_file_name_requester.cpp kis_double_parse_spin_box.cpp kis_double_parse_unit_spin_box.cpp kis_int_parse_spin_box.cpp KisColorSelectorInterface.cpp KoAnchorSelectionWidget.cpp squeezedcombobox.cpp KisGradientSlider.cpp KisGradientSliderWidget.cpp kis_color_input.cpp + # classes used by internal color selector kis_spinbox_color_selector.cpp - - KisDlgInternalColorSelector.cpp - KisVisualColorSelector.cpp KisVisualColorSelectorShape.cpp KisVisualEllipticalSelectorShape.cpp KisVisualRectangleSelectorShape.cpp KisVisualTriangleSelectorShape.cpp - kis_popup_button.cc - kis_color_button.cpp KisScreenColorPickerBase.cpp + KisDlgInternalColorSelector.cpp - KisColorsetChooser.cpp + KisPaletteModel.cpp + KisPaletteDelegate.cpp + kis_palette_view.cpp + KisPaletteListWidget.cpp + KisPaletteComboBox.cpp + + kis_popup_button.cc + kis_color_button.cpp ) ki18n_wrap_ui( kritawidgets_LIB_SRCS KoConfigAuthorPage.ui koDocumentInfoAboutWidget.ui koDocumentInfoAuthorWidget.ui - KoEditColorSet.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 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.cpp b/libs/widgets/KisColorsetChooser.cpp deleted file mode 100644 index 6fd9e2e91e..0000000000 --- a/libs/widgets/KisColorsetChooser.cpp +++ /dev/null @@ -1,161 +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. - */ - -#include "KisColorsetChooser.h" - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "kis_int_parse_spin_box.h" - -class ColorSetDelegate : public QAbstractItemDelegate -{ -public: - ColorSetDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) {} - ~ColorSetDelegate() override {} - /// reimplemented - void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; - /// reimplemented - QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { - return option.decorationSize; - } -}; - -void ColorSetDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const -{ - painter->save(); - if (! index.isValid()) - return; - - KoResource* resource = static_cast(index.internalPointer()); - KoColorSet* colorSet = static_cast(resource); - - if (option.state & QStyle::State_Selected) { - painter->fillRect(option.rect, option.palette.highlight()); - painter->setPen(option.palette.highlightedText().color()); - } - else { - painter->setBrush(option.palette.text().color()); - } - painter->drawText(option.rect.x() + 5, option.rect.y() + painter->fontMetrics().ascent() + 5, colorSet->name()); - - int size = 7; - for (quint32 i = 0; i < colorSet->nColors() && i*size < (quint32)option.rect.width(); i++) { - QRect rect(option.rect.x() + i*size, option.rect.y() + option.rect.height() - size, size, size); - painter->fillRect(rect, colorSet->getColorGlobal(i).color().toQColor()); - } - - painter->restore(); -} - -KisColorsetChooser::KisColorsetChooser(QWidget* parent): QWidget(parent) -{ - KoResourceServer * rserver = KoResourceServerProvider::instance()->paletteServer(); - QSharedPointer adapter(new KoResourceServerAdapter(rserver)); - m_itemChooser = new KoResourceItemChooser(adapter, this); - m_itemChooser->setItemDelegate(new ColorSetDelegate(this)); - m_itemChooser->showTaggingBar(true); - m_itemChooser->setFixedSize(250, 250); - m_itemChooser->setRowHeight(30); - m_itemChooser->setColumnCount(1); - connect(m_itemChooser, SIGNAL(resourceSelected(KoResource*)), - this, SLOT(resourceSelected(KoResource*))); - - KConfigGroup cfg = KSharedConfig::openConfig()->group(""); - m_itemChooser->configureKineticScrolling(cfg.readEntry("KineticScrollingGesture", 0), - cfg.readEntry("KineticScrollingSensitivity", 75), - cfg.readEntry("KineticScrollingScrollbar", true)); - - QPushButton* saveButton = new QPushButton(i18n("Save")); - connect(saveButton, SIGNAL(clicked(bool)), this, SLOT(slotSave())); - - m_nameEdit = new QLineEdit(this); - m_nameEdit->setPlaceholderText(i18n("Insert name")); - m_nameEdit->setClearButtonEnabled(true); - - m_columnEdit = new KisIntParseSpinBox(this); - m_columnEdit->setRange(1, 30); - m_columnEdit->setValue(10); - - QGridLayout* layout = new QGridLayout(this); - layout->addWidget(m_itemChooser, 0, 0, 1, 3); - layout->setColumnStretch(1, 1); - layout->addWidget(saveButton, 2, 2, 1, 1); - layout->addWidget(m_nameEdit, 1, 1, 1, 2); - layout->addWidget(new QLabel(i18n("Name:"), this), 1, 0, 1, 1); - layout->addWidget(m_columnEdit, 2, 1, 1, 1); - layout->addWidget(new QLabel(i18n("Columns:"), this), 2, 0, 1, 1); -} - -KisColorsetChooser::~KisColorsetChooser() -{ -} - -void KisColorsetChooser::resourceSelected(KoResource* resource) -{ - emit paletteSelected(static_cast(resource)); -} - -void KisColorsetChooser::slotSave() -{ - KoResourceServer * rserver = KoResourceServerProvider::instance()->paletteServer(); - - KoColorSet* colorset = new KoColorSet(); - colorset->setValid(true); - - QString saveLocation = rserver->saveLocation(); - QString name = m_nameEdit->text(); - int columns = m_columnEdit->value(); - - bool newName = false; - if(name.isEmpty()) { - newName = true; - name = i18n("Palette"); - } - QFileInfo fileInfo(saveLocation + name + colorset->defaultFileExtension()); - - int i = 1; - while (fileInfo.exists()) { - fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + colorset->defaultFileExtension()); - i++; - } - colorset->setFilename(fileInfo.filePath()); - if(newName) { - name = i18n("Palette %1", i); - } - colorset->setName(name); - colorset->setColumnCount(columns); - rserver->addResource(colorset); -} diff --git a/libs/widgets/KisDlgInternalColorSelector.cpp b/libs/widgets/KisDlgInternalColorSelector.cpp index 13a55d55f6..83d7a7488a 100644 --- a/libs/widgets/KisDlgInternalColorSelector.cpp +++ b/libs/widgets/KisDlgInternalColorSelector.cpp @@ -1,391 +1,344 @@ /* * 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 "squeezedcombobox.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; - KisColorsetChooser *colorSetChooser = 0; + KisPaletteListWidget *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); - this->setFocusPolicy(Qt::ClickFocus); + 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(); } - if (!m_d->paletteModel) { - m_d->paletteModel = new KisPaletteModel(this); - m_ui->paletteBox->setPaletteModel(m_d->paletteModel); - } - m_ui->bnColorsetChooser->setIcon(KisIconUtils::loadIcon("hi16-palette_library")); + m_d->paletteChooser = new KisPaletteListWidget(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(KoColorSet*)), this, SLOT(slotChangePalette(KoColorSet*))); + connect(m_ui->cmbNameList, SIGNAL(sigColorSelected(KoColor)), SLOT(slotColorUpdated(KoColor))); + // For some bizare 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(); KoColorSet *savedPal = rServer->resourceByName(paletteName); if (savedPal) { this->slotChangePalette(savedPal); } else { if (rServer->resources().count()) { savedPal = rServer->resources().first(); if (savedPal) { this->slotChangePalette(savedPal); } } } - connect(m_ui->paletteBox, SIGNAL(entrySelected(KoColorSetEntry)), this, SLOT(slotSetColorFromColorSetEntry(KoColorSetEntry))); - connect(m_ui->cmbNameList, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetColorFromColorList())); - //m_ui->paletteBox->setDisplayRenderer(displayRenderer); - m_d->colorSetChooser = new KisColorsetChooser(this); - connect(m_d->colorSetChooser, SIGNAL(paletteSelected(KoColorSet*)), this, SLOT(slotChangePalette(KoColorSet*))); - - m_ui->bnColorsetChooser->setPopupWidget(m_d->colorSetChooser); - + 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->bnColorsetChooser->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.")); } // screen color picker is in kritaui, so a dependency inversion is used to get 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(); } } connect(this, SIGNAL(signalForegroundColorChosen(KoColor)), this, SLOT(slotLockSelector())); 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())); connect(m_ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(this, SIGNAL(finished(int)), SLOT(slotFinishUp())); } KisDlgInternalColorSelector::~KisDlgInternalColorSelector() { delete m_ui; } void KisDlgInternalColorSelector::slotColorUpdated(KoColor newColor) { //if the update did not come from this selector... if (m_d->allowUpdates || QObject::sender() == this->parent()) { 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); 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); + 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::slotConfigurationChanged() { //m_d->canvas->displayColorConverter()-> //slotColorSpaceChanged(m_d->canvas->image()->colorSpace()); } void KisDlgInternalColorSelector::slotLockSelector() { m_d->allowUpdates = false; } 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 != this->parent()) { emit(signalForegroundColorChosen(m_d->currentColor)); m_d->compressColorChanges->start(); } if (m_d->screenColorPicker) { m_d->screenColorPicker->updateIcons(); } } void KisDlgInternalColorSelector::endUpdateWithNewColor() { 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(KoColorSet *set) { if (!set) { return; } - m_d->paletteModel->setColorSet(set); - m_ui->cmbNameList->clear(); - for (quint32 i = 0; i< set->nColors(); i++) { - KoColorSetEntry entry = set->getColorGlobal(i); - QPixmap colorSquare = QPixmap(32, 32); - if (entry.spotColor()) { - QImage img = QImage(32, 32, QImage::Format_ARGB32); - QPainter circlePainter; - img.fill(Qt::transparent); - circlePainter.begin(&img); - QBrush brush = QBrush(Qt::SolidPattern); - brush.setColor(entry.color().toQColor()); - circlePainter.setBrush(brush); - QPen pen = circlePainter.pen(); - pen.setColor(Qt::transparent); - pen.setWidth(0); - circlePainter.setPen(pen); - circlePainter.drawEllipse(0, 0, 32, 32); - circlePainter.end(); - colorSquare = QPixmap::fromImage(img); - } else { - colorSquare.fill(entry.color().toQColor()); - } - QString name = entry.name(); - if (!entry.id().isEmpty()){ - name = entry.id() + " - " + entry.name(); - } - m_ui->cmbNameList->addSqueezedItem(QIcon(colorSquare), name); - } - QCompleter *completer = new QCompleter(m_ui->cmbNameList->model()); - completer->setCompletionMode(QCompleter::PopupCompletion); - completer->setCaseSensitivity(Qt::CaseInsensitive); - completer->setFilterMode(Qt::MatchContains); - m_ui->cmbNameList->setCompleter(completer); -} - -void KisDlgInternalColorSelector::slotSetColorFromColorList() -{ - int index = m_ui->cmbNameList->currentIndex(); - if (m_d->paletteModel) { - slotSetColorFromColorSetEntry(m_d->paletteModel->colorSet()->getColorGlobal(index)); - m_ui->paletteBox->blockSignals(true); - m_ui->paletteBox->selectionModel()->clearSelection(); - m_ui->paletteBox->selectionModel()->setCurrentIndex(m_d->paletteModel->indexFromId(index), QItemSelectionModel::Select); - m_ui->paletteBox->blockSignals(false); - } -} - -void KisDlgInternalColorSelector::slotSetColorFromColorSetEntry(KoColorSetEntry entry) -{ - slotColorUpdated(entry.color()); + m_d->paletteModel->setPalette(set); } void KisDlgInternalColorSelector::showEvent(QShowEvent *event) { updateAllElements(0); QDialog::showEvent(event); } diff --git a/libs/widgets/KisDlgInternalColorSelector.h b/libs/widgets/KisDlgInternalColorSelector.h index b7dfd1473a..5454320499 100644 --- a/libs/widgets/KisDlgInternalColorSelector.h +++ b/libs/widgets/KisDlgInternalColorSelector.h @@ -1,197 +1,192 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISDLGINTERNALCOLORSELECTOR_H #define KISDLGINTERNALCOLORSELECTOR_H #include "kritawidgets_export.h" #include "KoColor.h" #include "KoColorSpace.h" #include "KoColorDisplayRendererInterface.h" #include "KoColorSet.h" #include #include "KisScreenColorPickerBase.h" #include "ui_WdgDlgInternalColorSelector.h" /** * @brief The KisInternalColorSelector class * * A non-modal color selector dialog that is not a plugin and can thus be used for filters. */ class KRITAWIDGETS_EXPORT KisDlgInternalColorSelector : public QDialog { Q_OBJECT public: static std::function s_screenColorPickerFactory; struct Config { Config() : modal(true), visualColorSelector(true), paletteBox(true), screenColorPicker(true), prevNextButtons(true), hexInput(true), useAlpha(false){} bool modal; bool visualColorSelector; bool paletteBox; bool screenColorPicker; bool prevNextButtons; bool hexInput; bool useAlpha; }; KisDlgInternalColorSelector(QWidget* parent, KoColor color, Config config, const QString &caption, const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance()); ~KisDlgInternalColorSelector() override; /** * @brief slotColorSpaceChanged * Color space has changed, use this dialog to change the colorspace. */ void colorSpaceChanged(const KoColorSpace *cs); /** * @brief lockUsedColorSpace * Lock the used colorspace of this selector. * @param cs */ void lockUsedColorSpace(const KoColorSpace *cs); /** * @brief setDisplayRenderer * Set the display renderer. This is necessary for HDR color manage support. * @param displayRenderer */ void setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer); /** * @brief getModalColorDialog * Execute this dialog modally. The function returns * the KoColor you want. * @param color - The current color. Make sure this is in the color space you want your * end color to be in. * @param chooseAlpha - Whether or not the alpha-choosing functionality should be used. */ static KoColor getModalColorDialog(const KoColor color, QWidget* parent = Q_NULLPTR, QString caption = QString()); /** * @brief getCurrentColor * @return gives currently active color; */ KoColor getCurrentColor(); void chooseAlpha(bool chooseAlpha); Q_SIGNALS: /** * @brief signalForegroundColorChosen * The most important signal. This will sent out when a color has been picked from the selector. * There will be a small delay to make sure that the selector causes too many updates. * * Do not connect this to slotColorUpdated. * @param color The new color chosen */ void signalForegroundColorChosen(KoColor color); public Q_SLOTS: /** * @brief slotColorUpdated * Very important slot. Is connected to krita's resources to make sure it has * the currently active color. It's very important that this function is able to understand * when the signal came from itself. * @param newColor This is the new color. */ void slotColorUpdated(KoColor newColor); /** * @brief slotSetColorFromPatch * update current color from kocolorpatch. * @param patch */ void slotSetColorFromPatch(KoColorPatch* patch); /** * @brief setPreviousColor * set the previous color. */ void setPreviousColor(KoColor c); void reject() override; private Q_SLOTS: /** * @brief slotLockSelector * This slot will prevent the color from being updated. */ void slotLockSelector(); /** * @brief slotConfigurationChanged * Wrapper slot for changes to the colorspace. */ void slotConfigurationChanged(); void endUpdateWithNewColor(); /** * @brief slotFinishUp * This is called when the selector is closed, for saving the current palette. */ void slotFinishUp(); /** * @brief slotSetColorFromHex * Update from the hex color input. */ void slotSetColorFromHex(); void slotChangePalette(KoColorSet *set); - void slotSetColorFromColorList(); - - void slotSetColorFromColorSetEntry(KoColorSetEntry entry); - - protected: void showEvent(QShowEvent *event) override; private: void focusInEvent(QFocusEvent *) override; /** * @brief updateAllElements * Updates each widget with the new element, and if it's responsible for the update sents * a signal out that there's a new color. */ void updateAllElements(QObject *source); private: Ui_WdgDlgInternalColorSelector *m_ui; struct Private; //The private struct const QScopedPointer m_d; //the private pointer }; #endif // KISDLGINTERNALCOLORSELECTOR_H diff --git a/libs/widgets/KisPaletteComboBox.cpp b/libs/widgets/KisPaletteComboBox.cpp new file mode 100644 index 0000000000..fe312017be --- /dev/null +++ b/libs/widgets/KisPaletteComboBox.cpp @@ -0,0 +1,162 @@ +/* + * 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. + */ + +// Qt +#include +#include +#include +#include + +// STL +#include + +#include "kis_palette_view.h" +#include "KisPaletteComboBox.h" + +KisPaletteComboBox::KisPaletteComboBox(QWidget *parent) + : QComboBox(parent) + , m_model(Q_NULLPTR) +{ + setEditable(true); + setInsertPolicy(NoInsert); + completer()->setCompletionMode(QCompleter::PopupCompletion); + completer()->setCaseSensitivity(Qt::CaseInsensitive); + completer()->setFilterMode(Qt::MatchContains); + connect(this, SIGNAL(currentIndexChanged(int)), SLOT(slotIndexUpdated(int))); +} + +KisPaletteComboBox::~KisPaletteComboBox() +{ } + +void KisPaletteComboBox::setPaletteModel(const KisPaletteModel *paletteModel) +{ + if (!m_model.isNull()) { + m_model->disconnect(this); + } + m_model = paletteModel; + if (m_model.isNull()) { return; } + slotPaletteChanged(); + connect(m_model, SIGNAL(sigPaletteChanged()), + SLOT(slotPaletteChanged())); + connect(m_model, SIGNAL(sigPaletteModified()), + SLOT(slotPaletteChanged())); +} + +void KisPaletteComboBox::setCompanionView(KisPaletteView *view) +{ + if (!m_view.isNull()) { + m_view->disconnect(this); + disconnect(m_view.data()); + } + m_view = view; + setPaletteModel(view->paletteModel()); + connect(view, SIGNAL(sigIndexSelected(QModelIndex)), + SLOT(slotSwatchSelected(QModelIndex))); + connect(this, SIGNAL(sigColorSelected(KoColor)), + view, SLOT(slotFGColorChanged(KoColor))); +} + +void KisPaletteComboBox::slotPaletteChanged() +{ + clear(); + m_groupMapMap.clear(); + m_idxSwatchMap.clear(); + + if (QPointer(m_model->colorSet()).isNull()) { return; } + + for (const QString &groupName : m_model->colorSet()->getGroupNames()) { + QVector infoList; + PosIdxMapType posIdxMap; + const KisSwatchGroup *group = m_model->colorSet()->getGroup(groupName); + for (const SwatchInfoType &info : group->infoList()) { + infoList.append(info); + } + std::sort(infoList.begin(), infoList.end(), swatchInfoLess); + for (const SwatchInfoType &info : infoList) { + const KisSwatch &swatch = info.swatch; + QString name = swatch.name(); + if (!swatch.id().isEmpty()){ + name = swatch.id() + " - " + swatch.name(); + } + addItem(QIcon(createColorSquare(swatch)), name); + posIdxMap[SwatchPosType(info.column, info.row)] = count() - 1; + m_idxSwatchMap.push_back(swatch); + } + m_groupMapMap[group->name()] = posIdxMap; + } + if (m_view.isNull()) { + setCurrentIndex(0); + } + QModelIndex idx = m_view->currentIndex(); + if (!idx.isValid()) { return; } + if (qvariant_cast(idx.data(KisPaletteModel::IsGroupNameRole))) { return; } + if (!qvariant_cast(idx.data(KisPaletteModel::CheckSlotRole))) { return; } + + blockSignals(true); // this is a passive selection; this shouldn't make others change + slotSwatchSelected(idx); + blockSignals(false); +} + +bool KisPaletteComboBox::swatchInfoLess(const SwatchInfoType &first, const SwatchInfoType &second) +{ + return first.swatch.name() < second.swatch.name(); +} + +QPixmap KisPaletteComboBox::createColorSquare(const KisSwatch &swatch) const +{ + QPixmap colorSquare(32, 32); + if (swatch.spotColor()) { + QImage img = QImage(32, 32, QImage::Format_ARGB32); + QPainter circlePainter; + img.fill(Qt::transparent); + circlePainter.begin(&img); + QBrush brush = QBrush(Qt::SolidPattern); + brush.setColor(swatch.color().toQColor()); + circlePainter.setBrush(brush); + QPen pen = circlePainter.pen(); + pen.setColor(Qt::transparent); + pen.setWidth(0); + circlePainter.setPen(pen); + circlePainter.drawEllipse(0, 0, 32, 32); + circlePainter.end(); + colorSquare = QPixmap::fromImage(img); + } else { + colorSquare.fill(swatch.color().toQColor()); + } + return colorSquare; +} + +void KisPaletteComboBox::slotSwatchSelected(const QModelIndex &index) +{ + if (!qvariant_cast(index.data(KisPaletteModel::CheckSlotRole))) { + return; + } + if (qvariant_cast(index.data(KisPaletteModel::IsGroupNameRole))) { + return; + } + QString gName = qvariant_cast(index.data(KisPaletteModel::GroupNameRole)); + int rowInGroup = qvariant_cast(index.data(KisPaletteModel::RowInGroupRole)); + setCurrentIndex(m_groupMapMap[gName][SwatchPosType(index.column(), rowInGroup)]); +} + +void KisPaletteComboBox::slotIndexUpdated(int idx) +{ + if (idx >= 0 && idx < m_idxSwatchMap.size()) { + emit sigColorSelected(m_idxSwatchMap[idx].color()); + } +} diff --git a/libs/widgets/KisPaletteComboBox.h b/libs/widgets/KisPaletteComboBox.h new file mode 100644 index 0000000000..e1d8d80e2a --- /dev/null +++ b/libs/widgets/KisPaletteComboBox.h @@ -0,0 +1,75 @@ +/* + * 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 KISPALETTECOMBOBOX_H +#define KISPALETTECOMBOBOX_H + +#include "kritawidgets_export.h" + +#include +#include +#include +#include +#include +#include + +#include + +class KisPaletteView; + +/** + * @brief The KisPaletteComboBox class + * A combobox used with KisPaletteView + * + */ +class KRITAWIDGETS_EXPORT KisPaletteComboBox : public QComboBox +{ + Q_OBJECT +private /* typedef */: + typedef KisSwatchGroup::SwatchInfo SwatchInfoType; + typedef QPair SwatchPosType; // first is column #, second is row # + typedef QHash PosIdxMapType; + +public: + explicit KisPaletteComboBox(QWidget *parent = Q_NULLPTR); + ~KisPaletteComboBox(); + +Q_SIGNALS: + void sigColorSelected(const KoColor &); + +public /* methods */: + void setCompanionView(KisPaletteView *); + +private Q_SLOTS: + void setPaletteModel(const KisPaletteModel *); + void slotPaletteChanged(); + void slotSwatchSelected(const QModelIndex &index); + void slotIndexUpdated(int); + +private /* methods */: + QPixmap createColorSquare(const KisSwatch &swatch) const; + static bool swatchInfoLess(const SwatchInfoType &, const SwatchInfoType &); + +private /* member variables */: + QPointer m_model; + QPointer m_view; + QHash m_groupMapMap; + QVector m_idxSwatchMap; +}; + +#endif // KISPALETTECOMBOBOX_H diff --git a/libs/widgets/KisPaletteDelegate.cpp b/libs/widgets/KisPaletteDelegate.cpp new file mode 100644 index 0000000000..01c5840008 --- /dev/null +++ b/libs/widgets/KisPaletteDelegate.cpp @@ -0,0 +1,111 @@ +/* + * 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 +#include +#include // qBound + +#include +#include +#include "kis_debug.h" + +#include "KisPaletteDelegate.h" + +const int KisPaletteDelegate::BORDER_WIDTH = 3; + +KisPaletteDelegate::KisPaletteDelegate(QObject *parent) + : QAbstractItemDelegate(parent) +{ } + +KisPaletteDelegate::~KisPaletteDelegate() +{ } + +void KisPaletteDelegate::paintCrossedLine(const QStyleOptionViewItem &option, QPainter *painter) const +{ + QRect crossRect = kisGrowRect(option.rect, -qBound(2, option.rect.width() / 6, 4)); + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setPen(QPen(Qt::white, 2.5)); + painter->drawLine(crossRect.topLeft(), crossRect.bottomRight()); + painter->setPen(QPen(Qt::red, 1.0)); + painter->drawLine(crossRect.topLeft(), crossRect.bottomRight()); + painter->restore(); +} + +void KisPaletteDelegate::paintNonCrossed(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index, const bool isSelected) const +{ +} + +void KisPaletteDelegate::paintGroupName(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index, const bool isSelected) const +{ + QString name = qvariant_cast(index.data(Qt::DisplayRole)); + if (isSelected) { + painter->fillRect(option.rect, option.palette.highlight()); + } + QRect paintRect = kisGrowRect(option.rect, -BORDER_WIDTH); + painter->setBrush(QBrush(Qt::lightGray)); + painter->drawText(paintRect, name); +} + +void KisPaletteDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + painter->save(); + + if (!index.isValid()) + return; + + const bool isSelected = option.state & QStyle::State_Selected; + + if (qvariant_cast(index.data(KisPaletteModel::IsGroupNameRole))) { + paintGroupName(painter, option, index, isSelected); + } else { + QRect paintRect = option.rect; + if (isSelected) { + painter->fillRect(option.rect, option.palette.highlight()); + paintRect = kisGrowRect(option.rect, -BORDER_WIDTH); + } + if (qvariant_cast(index.data(KisPaletteModel::CheckSlotRole))) { + QBrush brush = qvariant_cast(index.data(Qt::BackgroundRole)); + painter->fillRect(paintRect, brush); + } else { + QBrush lightBrush(Qt::gray); + QBrush darkBrush(Qt::darkGray); + painter->fillRect(paintRect, lightBrush); + painter->fillRect(QRect(paintRect.topLeft(), paintRect.center()), darkBrush); + painter->fillRect(QRect(paintRect.center(), paintRect.bottomRight()), darkBrush); + } + + QString name = qvariant_cast(index.data(Qt::DisplayRole)); + if (!m_crossedKeyword.isNull() && name.toLower().contains(m_crossedKeyword)) { + paintCrossedLine(option, painter); + } + } + + painter->restore(); +} + +QSize KisPaletteDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &) const +{ + return option.decorationSize; +} diff --git a/libs/widgets/kis_palette_delegate.h b/libs/widgets/KisPaletteDelegate.h similarity index 72% rename from libs/widgets/kis_palette_delegate.h rename to libs/widgets/KisPaletteDelegate.h index 3b85bbf9ea..466562e2a8 100644 --- a/libs/widgets/kis_palette_delegate.h +++ b/libs/widgets/KisPaletteDelegate.h @@ -1,42 +1,52 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_PALETTE_DELEGATE_H #define __KIS_PALETTE_DELEGATE_H #include #include "kritawidgets_export.h" class KRITAWIDGETS_EXPORT KisPaletteDelegate : public QAbstractItemDelegate { +private: + static const int BORDER_WIDTH; public: KisPaletteDelegate(QObject * parent = 0); ~KisPaletteDelegate() override; - void setCrossedKeyword(const QString &value); + void setCrossedKeyword(const QString &value) + { + m_crossedKeyword = value; + } void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override; + void paintSwatch() const; + private: QString m_crossedKeyword; + void paintCrossedLine(const QStyleOptionViewItem &option, QPainter *painter) const; + void paintNonCrossed(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, const bool isSelected) const; + void paintGroupName(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, const bool isSelected) const; }; #endif /* __KIS_PALETTE_DELEGATE_H */ diff --git a/libs/widgets/KisPaletteListWidget.cpp b/libs/widgets/KisPaletteListWidget.cpp new file mode 100644 index 0000000000..3e392c234d --- /dev/null +++ b/libs/widgets/KisPaletteListWidget.cpp @@ -0,0 +1,196 @@ +/* + * 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 "KisPaletteListWidget.h" +#include "KisPaletteListWidget_p.h" + +KisPaletteListWidget::KisPaletteListWidget(QWidget *parent) + : QWidget(parent) + , m_ui(new Ui_WdgPaletteListWidget) + , m_d(new KisPaletteListWidgetPrivate(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_d->model->setColumnCount(1); + + 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->setColumnCount(1); + 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(KoResource *)), SLOT(slotPaletteResourceSelected(KoResource*))); +} + +KisPaletteListWidget::~KisPaletteListWidget() +{ } + +void KisPaletteListWidget::slotPaletteResourceSelected(KoResource *r) +{ + KoColorSet *g = static_cast(r); + 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() +{ + if (!m_d->allowModification) { return; } + emit sigAddPalette(); + m_d->itemChooser->setCurrentItem(m_d->rAdapter->resources().size() - 1, 0); +} + +void KisPaletteListWidget::slotRemove() +{ + if (!m_d->allowModification) { return; } + if (m_d->itemChooser->currentResource()) { + KoColorSet *cs = static_cast(m_d->itemChooser->currentResource()); + emit sigRemovePalette(cs); + } + m_d->itemChooser->setCurrentItem(0, 0); +} + +void KisPaletteListWidget::slotImport() +{ + if (!m_d->allowModification) { return; } + emit sigImportPalette(); + m_d->itemChooser->setCurrentItem(m_d->rAdapter->resources().size()-1, 0); +} + +void KisPaletteListWidget::slotExport() +{ + if (!m_d->allowModification) { return; } + emit sigExportPalette(static_cast(m_d->itemChooser->currentResource())); +} + +void KisPaletteListWidget::setAllowModification(bool allowModification) +{ + m_d->allowModification = allowModification; + m_ui->bnAdd->setEnabled(allowModification); + m_ui->bnImport->setEnabled(allowModification); + m_ui->bnExport->setEnabled(allowModification); + KoColorSet *cs = static_cast(m_d->itemChooser->currentResource()); + m_ui->bnRemove->setEnabled(allowModification && cs && cs->isEditable()); +} + +/************************* KisPaletteListWidgetPrivate **********************/ + +KisPaletteListWidgetPrivate::KisPaletteListWidgetPrivate(KisPaletteListWidget *a_c) + : c(a_c) + , rAdapter(new KoResourceServerAdapter(KoResourceServerProvider::instance()->paletteServer())) + , itemChooser(new KoResourceItemChooser(rAdapter, a_c)) + , model(new Model(rAdapter, a_c)) + , delegate(new Delegate(a_c)) +{ } + +KisPaletteListWidgetPrivate::~KisPaletteListWidgetPrivate() +{ } + +/******************* KisPaletteListWidgetPrivate::Delegate ******************/ + +KisPaletteListWidgetPrivate::Delegate::Delegate(QObject *parent) + : QAbstractItemDelegate(parent) +{ } + +KisPaletteListWidgetPrivate::Delegate::~Delegate() +{ } + +void KisPaletteListWidgetPrivate::Delegate::paint(QPainter * painter, + const QStyleOptionViewItem & option, + const QModelIndex & index) const +{ + painter->save(); + if (!index.isValid()) + return; + + KoResource* resource = static_cast(index.internalPointer()); + KoColorSet* colorSet = static_cast(resource); + + QRect previewRect(option.rect.x() + 2, + option.rect.y() + 2, + option.rect.height() - 4, + option.rect.height() - 4); + + painter->drawImage(previewRect, colorSet->image()); + + if (option.state & QStyle::State_Selected) { + painter->fillRect(option.rect, option.palette.highlight()); + painter->drawImage(previewRect, colorSet->image()); + painter->setPen(option.palette.highlightedText().color()); + } else { + painter->setBrush(option.palette.text().color()); + } + QString drawnText = colorSet->name() + + (colorSet->isEditable() ? "" : i18n(" [READONLY]")); + painter->drawText(option.rect.x() + previewRect.width() + 10, + option.rect.y() + painter->fontMetrics().ascent() + 5, + drawnText); + + painter->restore(); +} + +inline QSize KisPaletteListWidgetPrivate::Delegate::sizeHint(const QStyleOptionViewItem & option, + const QModelIndex &) const +{ + return option.decorationSize; +} diff --git a/libs/widgets/KisPaletteListWidget.h b/libs/widgets/KisPaletteListWidget.h new file mode 100644 index 0000000000..482caf3b88 --- /dev/null +++ b/libs/widgets/KisPaletteListWidget.h @@ -0,0 +1,68 @@ +/* + * 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" + +class KoResource; +class KoColorSet; + +class KisPaletteListWidgetPrivate; + +class KRITAWIDGETS_EXPORT KisPaletteListWidget : public QWidget +{ + Q_OBJECT +public: + explicit KisPaletteListWidget(QWidget *parent = nullptr); + virtual ~KisPaletteListWidget(); + +public: + void setAllowModification(bool allowModification); + +Q_SIGNALS: + void sigPaletteSelected(KoColorSet*); + void sigAddPalette(); + void sigRemovePalette(KoColorSet *); + void sigImportPalette(); + void sigExportPalette(KoColorSet *); + +public Q_SLOTS: + +private /* methods */: + QString newPaletteFileName(); + +private Q_SLOTS: + void slotPaletteResourceSelected(KoResource *); + void slotAdd(); + void slotRemove(); + void slotImport(); + void slotExport(); + +private: + QScopedPointer m_ui; + QScopedPointer m_d; +}; + +#endif // KISPALETTELISTWIDGET_H diff --git a/libs/widgets/KisPaletteListWidget_p.h b/libs/widgets/KisPaletteListWidget_p.h new file mode 100644 index 0000000000..fcf6af4cf2 --- /dev/null +++ b/libs/widgets/KisPaletteListWidget_p.h @@ -0,0 +1,67 @@ +#ifndef KISPALETTELISTWIDGET_P_H +#define KISPALETTELISTWIDGET_P_H + +#include +#include +#include +#include +#include +#include + +#include "KisPaletteListWidget.h" +#include "KoResourceModel.h" +#include "KoResourceItemView.h" +#include "KoResourceItemChooser.h" +#include "KoResourceServer.h" +#include "KoResourceServerAdapter.h" +#include "KoResourceServerProvider.h" +#include "KoColorSet.h" + +struct KisPaletteListWidgetPrivate +{ + class View; + class Delegate; + class Model; + KisPaletteListWidgetPrivate(KisPaletteListWidget *); + virtual ~KisPaletteListWidgetPrivate(); + + bool allowModification; + + QPointer c; + + QSharedPointer > rAdapter; + QSharedPointer itemChooser; + + QScopedPointer model; + QScopedPointer delegate; + + QScopedPointer actAdd; + QScopedPointer actImport; + QScopedPointer actExport; + QScopedPointer actModify; + QScopedPointer actRemove; +}; + +class KisPaletteListWidgetPrivate::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; +}; + +class KisPaletteListWidgetPrivate::Model : public KoResourceModel +{ +public: + Model(const QSharedPointer > &rAdapter, QObject *parent = Q_NULLPTR) + : KoResourceModel(rAdapter, parent) + { } + ~Model() override { } + + Qt::ItemFlags flags(const QModelIndex &index) const override + { return KoResourceModel::flags(index) | Qt::ItemIsUserCheckable; } +}; +#endif // KISPALETTELISTWIDGET_P_H diff --git a/libs/widgets/KisPaletteModel.cpp b/libs/widgets/KisPaletteModel.cpp index 1188627a78..f106d10d39 100644 --- a/libs/widgets/KisPaletteModel.cpp +++ b/libs/widgets/KisPaletteModel.cpp @@ -1,649 +1,508 @@ /* * 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 KisPaletteModel::KisPaletteModel(QObject* parent) - : QAbstractTableModel(parent), - m_colorSet(0), - m_displayRenderer(KoDumbColorDisplayRenderer::instance()) + : QAbstractTableModel(parent) + , m_colorSet(Q_NULLPTR) + , m_displayRenderer(KoDumbColorDisplayRenderer::instance()) { + connect(this, SIGNAL(sigPaletteModified()), SLOT(slotPaletteModified())); } KisPaletteModel::~KisPaletteModel() { } -void KisPaletteModel::setDisplayRenderer(KoColorDisplayRendererInterface *displayRenderer) -{ - if (displayRenderer) { - if (m_displayRenderer) { - disconnect(m_displayRenderer, 0, this, 0); - } - m_displayRenderer = displayRenderer; - connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), - SLOT(slotDisplayConfigurationChanged())); - } else { - m_displayRenderer = KoDumbColorDisplayRenderer::instance(); - } -} - -void KisPaletteModel::slotDisplayConfigurationChanged() -{ - beginResetModel(); - endResetModel(); -} - -QModelIndex KisPaletteModel::getLastEntryIndex() -{ - int endRow = rowCount(); - int endColumn = columnCount(); - if (m_colorSet->nColors()>0) { - QModelIndex i = this->index(endRow, endColumn, QModelIndex()); - while (qvariant_cast(i.data(RetrieveEntryRole)).isEmpty()) { - i = this->index(endRow, endColumn); - endColumn -=1; - if (endColumn<0) { - endColumn = columnCount(); - endRow-=1; - } - } - return i; - } - return QModelIndex(); -} - QVariant KisPaletteModel::data(const QModelIndex& index, int role) const { - KoColorSetEntry entry; - if (m_colorSet && m_displayRenderer) { - //now to figure out whether we have a groupname row or not. - bool groupNameRow = false; - quint32 indexInGroup = 0; - QString indexGroupName = QString(); - - int rowstotal = m_colorSet->nColorsGroup()/columnCount(); - if (index.row()<=rowstotal && (quint32)(index.row()*columnCount()+index.column())nColorsGroup()) { - indexInGroup = (quint32)(index.row()*columnCount()+index.column()); - } - if (m_colorSet->nColorsGroup()==0) { - rowstotal+=1; //always add one for the default group when considering groups. - } - Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){ - //we make an int for the rows added by the current group. - int newrows = 1+m_colorSet->nColorsGroup(groupName)/columnCount(); - if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { - newrows+=1; - } - if (newrows==0) { - newrows+=1; //always add one for the group when considering groups. - } - quint32 tempIndex = (quint32)((index.row()-(rowstotal+2))*columnCount()+index.column()); - if (index.row() == rowstotal+1) { - //rowstotal+1 is taken up by the groupname. - indexGroupName = groupName; - groupNameRow = true; - } else if (index.row() > (rowstotal+1) && index.row() <= rowstotal+newrows && - tempIndexnColorsGroup(groupName)){ - //otherwise it's an index to the colors in the group. - indexGroupName = groupName; - indexInGroup = tempIndex; - } - //add the new rows to the totalrows we've looked at. - rowstotal += newrows; - } - if (groupNameRow) { - switch (role) { - case Qt::ToolTipRole: - case Qt::DisplayRole: { - return indexGroupName; - } - case IsHeaderRole: { - return true; - } - case RetrieveEntryRole: { - QStringList entryList; - entryList.append(indexGroupName); - entryList.append(QString::number(0)); - return entryList; - } - } - } else { - if (indexInGroup < m_colorSet->nColorsGroup(indexGroupName)) { - entry = m_colorSet->getColorGroup(indexInGroup, indexGroupName); - switch (role) { - case Qt::ToolTipRole: - case Qt::DisplayRole: { - return entry.name(); - } - case Qt::BackgroundRole: { - QColor color = m_displayRenderer->toQColor(entry.color()); - return QBrush(color); - } - case IsHeaderRole: { - return false; - } - case RetrieveEntryRole: { - QStringList entryList; - entryList.append(indexGroupName); - entryList.append(QString::number(indexInGroup)); - return entryList; - } - } - } - } + 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); } - return QVariant(); } int KisPaletteModel::rowCount(const QModelIndex& /*parent*/) const { - if (!m_colorSet) { - return 0; - } - if (m_colorSet->nColors()==0) { + if (!m_colorSet) return 0; - } - if (columnCount() > 0) { - int countedrows = m_colorSet->nColorsGroup("")/columnCount(); - if (m_colorSet->nColorsGroup()%columnCount() > 0) { - countedrows+=1; - } - if (m_colorSet->nColorsGroup()==0) { - countedrows+=1; - } - Q_FOREACH (QString groupName, m_colorSet->getGroupNames()) { - countedrows += 1; //add one for the name; - countedrows += 1+(m_colorSet->nColorsGroup(groupName)/ columnCount()); - if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { - countedrows+=1; - } - if (m_colorSet->nColorsGroup(groupName)==0) { - countedrows+=1; - } - } - countedrows +=1; //Our code up till now doesn't take 0 into account. - return countedrows; - } - return m_colorSet->nColors()/15 + 1; + 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(); } - return 15; + 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::ItemIsSelectable | + Qt::ItemIsEnabled | + Qt::ItemIsUserCheckable | + Qt::ItemIsDragEnabled | + Qt::ItemIsDropEnabled; } return Qt::ItemIsDropEnabled; } QModelIndex KisPaletteModel::index(int row, int column, const QModelIndex& parent) const { - if (m_colorSet) { + Q_UNUSED(parent); + Q_ASSERT(m_colorSet); + int groupNameRow = groupNameRowForRow(row); + KisSwatchGroup *group = m_colorSet->getGroup(m_rowGroupNameMap[groupNameRow]); + Q_ASSERT(group); + return createIndex(row, column, group); +} - //make an int to hold the amount of rows we've looked at. The initial is the total rows in the default group. - int rowstotal = m_colorSet->nColorsGroup()/columnCount(); - if (row<=rowstotal && (quint32)(row*columnCount()+column)nColorsGroup()) { - //if the total rows are in the default group, we just return an index. - return QAbstractTableModel::index(row, column, parent); - } else if(row<0 && column<0) { - return QAbstractTableModel::index(0, 0, parent); - } - if (m_colorSet->nColorsGroup()==0) { - rowstotal+=1; //always add one for the default group when considering groups. - } - Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){ - //we make an int for the rows added by the current group. - int newrows = 1+m_colorSet->nColorsGroup(groupName)/columnCount(); - if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { - newrows+=1; - } - if (m_colorSet->nColorsGroup(groupName)==0) { - newrows+=1; //always add one for the group when considering groups. - } - if (rowstotal + newrows>rowCount()) { - newrows = rowCount() - rowstotal; - } - quint32 tempIndex = (quint32)((row-(rowstotal+2))*columnCount()+column); - if (row == rowstotal+1) { - //rowstotal+1 is taken up by the groupname. - return QAbstractTableModel::index(row, 0, parent); - } else if (row > (rowstotal+1) && row <= rowstotal+newrows && tempIndexnColorsGroup(groupName)){ - //otherwise it's an index to the colors in the group. - return QAbstractTableModel::index(row, column, parent); - } - //add the new rows to the totalrows we've looked at. - rowstotal += newrows; - } +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 } - return QModelIndex(); } -void KisPaletteModel::setColorSet(KoColorSet* colorSet) +void KisPaletteModel::setPalette(KoColorSet* palette) { - m_colorSet = colorSet; beginResetModel(); + m_colorSet = palette; + if (palette) { + resetGroupNameRows(); + } endResetModel(); + emit sigPaletteChanged(); } KoColorSet* KisPaletteModel::colorSet() const { return m_colorSet; } -QModelIndex KisPaletteModel::indexFromId(int i) const +int KisPaletteModel::rowNumberInGroup(int rowInModel) const { - QModelIndex index = QModelIndex(); - if (!colorSet() || colorSet()->nColors() == 0) { - return index; - } - - if (i > (int)colorSet()->nColors()) { - qWarning()<<"index is too big"<nColors(); - index = this->index(0,0); - } - - if (i < (int)colorSet()->nColorsGroup(0)) { - index = QAbstractTableModel::index(i/columnCount(), i % columnCount()); - if (!index.isValid()) { - index = QAbstractTableModel::index(0, 0, QModelIndex()); - } - return index; - } else { - int rowstotal = 1 + m_colorSet->nColorsGroup() / columnCount(); - if (m_colorSet->nColorsGroup() == 0) { - rowstotal += 1; - } - int totalIndexes = colorSet()->nColorsGroup(); - Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){ - if (i + 1 <= (int)(totalIndexes + colorSet()->nColorsGroup(groupName)) && i + 1 > (int)totalIndexes) { - int col = (i - totalIndexes) % columnCount(); - int row = rowstotal + 1 + ((i - totalIndexes) / columnCount()); - index = this->index(row, col); - return index; - } else { - rowstotal += 1 + m_colorSet->nColorsGroup(groupName) / columnCount(); - totalIndexes += colorSet()->nColorsGroup(groupName); - if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) { - rowstotal += 1; - } - if (m_colorSet->nColorsGroup(groupName)==0) { - rowstotal += 1; //always add one for the group when considering groups. - } - } - } - } - return index; -} - -int KisPaletteModel::idFromIndex(const QModelIndex &index) const -{ - if (index.isValid()==false) { + if (m_rowGroupNameMap.contains(rowInModel)) { return -1; - qWarning()<<"invalid index"; } - int i=0; - QStringList entryList = qvariant_cast(data(index, RetrieveEntryRole)); - if (entryList.isEmpty()) { - return -1; - qWarning()<<"invalid index, there's no data to retrieve here"; - } - if (entryList.at(0)==QString()) { - return entryList.at(1).toUInt(); - } - - i = colorSet()->nColorsGroup(""); - //find at which position the group is. - int groupIndex = colorSet()->getGroupNames().indexOf(entryList.at(0)); - //add all the groupsizes onto it till we get to our group. - for(int g=0; gnColorsGroup(colorSet()->getGroupNames().at(g)); + QList rowNumberList = m_rowGroupNameMap.keys(); + for (auto it = rowNumberList.rbegin(); it != rowNumberList.rend(); it++) { + if (*it < rowInModel) { + return rowInModel - *it - 1; + } } - //then add the index. - i += entryList.at(1).toUInt(); - return i; + return rowInModel; } -KoColorSetEntry KisPaletteModel::colorSetEntryFromIndex(const QModelIndex &index) const +int KisPaletteModel::groupNameRowForName(const QString &groupName) { - KoColorSetEntry blank = KoColorSetEntry(); - if (!index.isValid()) { - return blank; - } - QStringList entryList = qvariant_cast(data(index, RetrieveEntryRole)); - if (entryList.isEmpty()) { - return blank; + for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) { + if (it.value() == groupName) { + return it.key(); + } } - QString groupName = entryList.at(0); - quint32 indexInGroup = entryList.at(1).toUInt(); - return m_colorSet->getColorGroup(indexInGroup, groupName); + return -1; } -bool KisPaletteModel::addColorSetEntry(KoColorSetEntry entry, QString groupName) +bool KisPaletteModel::addEntry(const KisSwatch &entry, const QString &groupName) { - int col = m_colorSet->nColorsGroup(groupName)%columnCount(); - QModelIndex i = getLastEntryIndex(); - if (col+1 > columnCount()) { - beginInsertRows(QModelIndex(), i.row(), i.row()+1); - } - if ((int)m_colorSet->nColors() < columnCount()) { - beginInsertColumns(QModelIndex(), m_colorSet->nColors(), m_colorSet->nColors()+1); - } + beginInsertRows(QModelIndex(), rowCount(), rowCount() + 1); m_colorSet->add(entry, groupName); - if (col + 1 > columnCount()) { - endInsertRows(); - } - if (m_colorSet->nColors() < (quint32)columnCount()) { - endInsertColumns(); + endInsertRows(); + if (m_colorSet->isGlobal()) { + m_colorSet->save(); } + emit sigPaletteModified(); return true; } -bool KisPaletteModel::removeEntry(QModelIndex index, bool keepColors) +bool KisPaletteModel::removeEntry(const QModelIndex &index, bool keepColors) { - QStringList entryList = qvariant_cast(index.data(RetrieveEntryRole)); - if (entryList.empty()) { - return false; - } - QString groupName = entryList.at(0); - quint32 indexInGroup = entryList.at(1).toUInt(); - - if (qvariant_cast(index.data(IsHeaderRole)) == false) { - - bool allowRemoveRow = false; - - if (index.column()-1<0 - && m_colorSet->nColorsGroup(groupName)%columnCount() <1 - && index.row()-1>0 - && m_colorSet->nColorsGroup(groupName)/columnCount()>0) { - - allowRemoveRow = true; - } - - if (allowRemoveRow) { - beginRemoveRows(QModelIndex(), index.row(), index.row()-1); - } - - m_colorSet->removeAt(indexInGroup, groupName); - - if (allowRemoveRow) { - endRemoveRows(); - } + if (!qvariant_cast(data(index, IsGroupNameRole))) { + static_cast(index.internalPointer())->removeEntry(index.column(), + rowNumberInGroup(index.row())); + emit dataChanged(index, index); } else { - beginRemoveRows(QModelIndex(), index.row(), index.row()-1); - m_colorSet->removeGroup(groupName, keepColors); - endRemoveRows(); + int groupNameRow = groupNameRowForRow(index.row()); + QString groupName = m_rowGroupNameMap[groupNameRow]; + removeGroup(groupName, keepColors); } + emit sigPaletteModified(); return true; } -bool KisPaletteModel::addGroup(QString groupName) -{ - QModelIndex i = getLastEntryIndex(); - beginInsertRows(QModelIndex(), i.row(), i.row()+1); - m_colorSet->addGroup(groupName); - endInsertRows(); - return true; -} - -bool KisPaletteModel::removeRows(int row, int count, const QModelIndex &parent) +void KisPaletteModel::removeGroup(const QString &groupName, bool keepColors) { - Q_ASSERT(!parent.isValid()); - - int beginRow = qMax(0, row); - int endRow = qMin(row + count - 1, (int)m_colorSet->nColors() - 1); - beginRemoveRows(parent, beginRow, endRow); - - // Find the palette entry at row, count, remove from KoColorSet - + 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(); - return true; + beginInsertRows(QModelIndex(), + 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; } - int endRow; - int endColumn; - - if (!parent.isValid()) { - if (row < 0) { - endRow = indexFromId(m_colorSet->nColors()).row(); - endColumn = indexFromId(m_colorSet->nColors()).column(); - } else { - endRow = qMin(row, indexFromId(m_colorSet->nColors()).row()); - endColumn = qMin(column, m_colorSet->columnCount()); - } - } else { - endRow = qMin(parent.row(), rowCount()); - endColumn = qMin(parent.column(), columnCount()); - } + 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 groupName; - stream >> groupName; - QModelIndex index = this->index(endRow, 0); - if (index.isValid()) { - QStringList entryList = qvariant_cast(index.data(RetrieveEntryRole)); - QString groupDroppedOn = QString(); - if (!entryList.isEmpty()) { - groupDroppedOn = entryList.at(0); - } - int groupIndex = colorSet()->getGroupNames().indexOf(groupName); - beginMoveRows( QModelIndex(), groupIndex, groupIndex, QModelIndex(), endRow); - m_colorSet->moveGroup(groupName, groupDroppedOn); + 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(); - endMoveRows(); - - ++endRow; } } - } else { - QByteArray encodedData = data->data("krita/x-colorsetentry"); - QDataStream stream(&encodedData, QIODevice::ReadOnly); + return true; + } - while (!stream.atEnd()) { - KoColorSetEntry entry; - QString oldGroupName; - int indexInGroup; - QString colorXml; - - QString name, id; - bool spotColor; - stream >> name - >> id - >> spotColor - >> indexInGroup - >> oldGroupName - >> colorXml; - entry.setName(name); - entry.setId(id); - entry.setSpotColor(spotColor); + if (qvariant_cast(finalIndex.data(KisPaletteModel::IsGroupNameRole))) { + return true; + } - 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)); - } + 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)); + } - QModelIndex index = this->index(endRow, endColumn); - if (qvariant_cast(index.data(IsHeaderRole))){ - endRow+=1; - } - if (index.isValid()) { - /*this is to figure out the row of the old color. - * That way we can in turn avoid moverows from complaining the - * index is out of bounds when using index. - * Makes me wonder if we shouldn't just insert the index of the - * old color when requesting the mimetype... - */ - int i = indexInGroup; - if (oldGroupName != QString()) { - colorSet()->nColorsGroup(""); - //find at which position the group is. - int groupIndex = colorSet()->getGroupNames().indexOf(oldGroupName); - //add all the groupsizes onto it till we get to our group. - for(int g=0; gnColorsGroup(colorSet()->getGroupNames().at(g)); - } - } - QModelIndex indexOld = indexFromId(i); - if (action == Qt::MoveAction){ - if (indexOld.row()!=qMax(endRow, 0) && indexOld.row()!=qMax(endRow+1,1)) { - beginMoveRows(QModelIndex(), indexOld.row(), indexOld.row(), QModelIndex(), qMax(endRow+1,1)); - } - if (indexOld.column()!=qMax(endColumn, 0) && indexOld.column()!=qMax(endColumn+1,1)) { - beginMoveColumns(QModelIndex(), indexOld.column(), indexOld.column(), QModelIndex(), qMax(endColumn+1,1)); - } + 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 { - beginInsertRows(QModelIndex(), endRow, endRow); - } - QStringList entryList = qvariant_cast(index.data(RetrieveEntryRole)); - QString entryInGroup = "0"; - QString groupName = QString(); - if (!entryList.isEmpty()) { - groupName = entryList.at(0); - entryInGroup = entryList.at(1); - } - - int location = entryInGroup.toInt(); - // Insert the entry - if (groupName==oldGroupName && qvariant_cast(index.data(IsHeaderRole))==true) { - groupName=QString(); - location=m_colorSet->nColorsGroup(); - } - m_colorSet->insertBefore(entry, location, groupName); - if (groupName==oldGroupName && locationremoveAt(indexInGroup, oldGroupName); + g->removeEntry(oriColumn, oriRow); } + } + setEntry(entry, finalIndex); + emit sigPaletteModified(); + if (m_colorSet->isGlobal()) { m_colorSet->save(); - if (action == Qt::MoveAction){ - if (indexOld.row()!=qMax(endRow, 0) && indexOld.row()!=qMax(endRow+1,1)) { - endMoveRows(); - } - if (indexOld.column()!=qMax(endColumn, 0) && indexOld.column()!=qMax(endColumn+1,1)) { - endMoveColumns(); - } - - } else { - endInsertRows(); - } - - ++endRow; } } } return true; } QMimeData *KisPaletteModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); - QString mimeTypeName = "krita/x-colorsetentry"; - //Q_FOREACH(const QModelIndex &index, indexes) { QModelIndex index = indexes.last(); - if (index.isValid()) { - if (qvariant_cast(index.data(IsHeaderRole))==false) { - KoColorSetEntry entry = colorSetEntryFromIndex(index); - QStringList entryList = qvariant_cast(index.data(RetrieveEntryRole)); - QString groupName = QString(); - int indexInGroup = 0; - if (!entryList.isEmpty()) { - groupName = entryList.at(0); - QString iig = entryList.at(1); - indexInGroup = iig.toInt(); - } + 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() - << indexInGroup - << groupName + stream << entry.name() << entry.id() << entry.spotColor() + << rowNumberInGroup(index.row()) << index.column() + << qvariant_cast(index.data(GroupNameRole)) << doc.toString(); } else { mimeTypeName = "krita/x-colorsetgroup"; - QStringList entryList = qvariant_cast(index.data(RetrieveEntryRole)); - QString groupName = QString(); - if (!entryList.isEmpty()) { - groupName = entryList.at(0); - } + QString groupName = qvariant_cast(index.data(GroupNameRole)); stream << groupName; } + mimeData->setData(mimeTypeName, encodedData); } - 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(); + } +} + +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())); + } else { + m_displayRenderer = KoDumbColorDisplayRenderer::instance(); + } +} + +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 64451c58cc..9bbdf0a570 100644 --- a/libs/widgets/KisPaletteModel.h +++ b/libs/widgets/KisPaletteModel.h @@ -1,149 +1,179 @@ /* * 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 kis_palette_view and kis_palette_delegate forms a mvc way to access kocolorsets. + * 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: - KisPaletteModel(QObject* parent = 0); + explicit KisPaletteModel(QObject* parent = Q_NULLPTR); ~KisPaletteModel() override; enum AdditionalRoles { - IsHeaderRole = Qt::UserRole + 1, - ExpandCategoryRole = Qt::UserRole + 2, - RetrieveEntryRole = Qt::UserRole + 3 + IsGroupNameRole = Qt::UserRole + 1, + CheckSlotRole, + GroupNameRole, + RowInGroupRole }; +public /* overriden 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; - Qt::ItemFlags flags(const QModelIndex& index) 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; - void setColorSet(KoColorSet* colorSet); - KoColorSet* colorSet() const; + Qt::ItemFlags flags(const QModelIndex& index) const override; /** - * Installs a display renderer object for a palette that will - * convert the KoColor to the displayable QColor. Default is the - * dumb renderer. + * @brief dropMimeData + * This is an overridden function that handles dropped mimedata. + * right now only colorsetentries and colorsetgroups are handled. + * @return */ - void setDisplayRenderer(KoColorDisplayRendererInterface *displayRenderer); - + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) override; /** - * @brief indexFromId - * convenience function to get the tableindex from the global palette color. - * used by lazybrush. - * @param i - * @return index in table. + * @brief mimeData + * gives the mimedata for a kocolorsetentry or a kocolorsetgroup. + * @param indexes + * @return the mimedata for the given indices */ - QModelIndex indexFromId(int i) const; + QMimeData *mimeData(const QModelIndexList &indexes) const override; + + QStringList mimeTypes() const override; + + Qt::DropActions supportedDropActions() const override; /** - * @brief idFromIndex - * convenience function to get the global colorset entry id from the table index. - * If you just want to use this to get the kocolorsetentry, use colorsetEntryFromIndex instead. - * @param index - * @return + * @brief setData + * setData is not used as KoColor is not a QVariant + * use setEntry, addEntry and removeEntry instead */ - int idFromIndex(const QModelIndex &index) const; + // 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 colorSetEntryFromIndex - * This gives the colorset entry for the given table model index. - * @param index the QModelIndex - * @return the kocolorsetentry + * @brief sigPaletteChanged + * emitted when the palette associated with the model is made another one */ - KoColorSetEntry colorSetEntryFromIndex(const QModelIndex &index) const; + void sigPaletteChanged(); +public /* methods */: /** - * @brief addColorSetEntry + * @brief addEntry * proper function to handle adding entries. * @return whether successful. */ - bool addColorSetEntry(KoColorSetEntry entry, QString groupName=QString()); + 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 consolidtes both removeentry and removegroup. * @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(QModelIndex index, bool keepColors=true); - /** - * @brief addGroup - * Adds a group to the list. - * @param groupName - * @return if successful - */ - bool addGroup(QString groupName = QString()); + 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(); - bool removeRows(int row, int count, const QModelIndex &parent) override; + KisSwatch getEntry(const QModelIndex &index) const; - /** - * @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; + void setPalette(KoColorSet* colorSet); + KoColorSet* 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); + + +private /* member variables */: + QPointer m_colorSet; + QPointer m_displayRenderer; + QMap m_rowGroupNameMap; -private: - KoColorSet* m_colorSet; - QPointer m_displayRenderer; - QModelIndex getLastEntryIndex(); +friend class KisPaletteView; }; #endif diff --git a/libs/widgets/KoColorPopupAction.cpp b/libs/widgets/KoColorPopupAction.cpp index d8312360ed..e78bd396ad 100644 --- a/libs/widgets/KoColorPopupAction.cpp +++ b/libs/widgets/KoColorPopupAction.cpp @@ -1,259 +1,247 @@ /* This file is part of the KDE project * Copyright (c) 2007 C. Boemann * Copyright (C) 2007 Fredy Yanardi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColorPopupAction.h" #include "KoColorSetWidget.h" #include "KoTriangleColorSelector.h" #include "KoColorSlider.h" #include "KoCheckerBoardPainter.h" #include "KoResourceServer.h" #include "KoResourceServerProvider.h" #include #include #include #include #include #include #include #include #include #include #include class KoColorPopupAction::KoColorPopupActionPrivate { public: KoColorPopupActionPrivate() : colorSetWidget(0) , colorChooser(0) , opacitySlider(0) , menu(0) , checkerPainter(4) , showFilter(true) , applyMode(true) , firstTime(true) {} ~KoColorPopupActionPrivate() { delete menu; } KoColor currentColor; KoColor buddyColor; KoColorSetWidget *colorSetWidget; KoTriangleColorSelector * colorChooser; KoColorSlider * opacitySlider; QMenu *menu; KoCheckerBoardPainter checkerPainter; bool showFilter; bool applyMode; bool firstTime; }; KoColorPopupAction::KoColorPopupAction(QObject *parent) : QAction(parent), d(new KoColorPopupActionPrivate()) { d->menu = new QMenu(); QWidget *widget = new QWidget(d->menu); QWidgetAction *wdgAction = new QWidgetAction(d->menu); d->colorSetWidget = new KoColorSetWidget(widget); KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); QPointer defaultColorSet = rServer->resourceByName("Default"); if (!defaultColorSet && rServer->resources().count() > 0) { defaultColorSet = rServer->resources().first(); } d->colorSetWidget->setColorSet(defaultColorSet); d->colorChooser = new KoTriangleColorSelector( widget ); // prevent mouse release on color selector from closing popup d->colorChooser->setAttribute( Qt::WA_NoMousePropagation ); d->opacitySlider = new KoColorSlider( Qt::Vertical, widget ); d->opacitySlider->setFixedWidth(25); d->opacitySlider->setRange(0, 255); d->opacitySlider->setValue(255); d->opacitySlider->setToolTip( i18n( "Opacity" ) ); QGridLayout * layout = new QGridLayout( widget ); layout->addWidget( d->colorSetWidget, 0, 0, 1, -1 ); layout->addWidget( d->colorChooser, 1, 0 ); layout->addWidget( d->opacitySlider, 1, 1 ); layout->setMargin(4); wdgAction->setDefaultWidget(widget); d->menu->addAction(wdgAction); setMenu(d->menu); new QHBoxLayout(d->menu); d->menu->layout()->addWidget(widget); d->menu->layout()->setMargin(0); connect(this, SIGNAL(triggered()), this, SLOT(emitColorChanged())); - connect(d->colorSetWidget, SIGNAL(colorChanged(KoColor,bool)), this, SLOT(colorWasSelected(KoColor,bool))); - - connect( d->colorChooser, SIGNAL(colorChanged(QColor)), - this, SLOT(colorWasEdited(QColor)) ); - connect( d->opacitySlider, SIGNAL(valueChanged(int)), - this, SLOT(opacityWasChanged(int))); + connect(d->colorSetWidget, SIGNAL(colorChanged(const KoColor &, bool)), + this, SLOT(colorWasSelected(const KoColor &, bool))); + connect(d->colorChooser, SIGNAL(colorChanged(const QColor &)), + this, SLOT(colorWasEdited( const QColor &))); + connect(d->opacitySlider, SIGNAL(valueChanged(int)), + this, SLOT(opacityWasChanged(int))); } KoColorPopupAction::~KoColorPopupAction() { delete d; } void KoColorPopupAction::setCurrentColor( const KoColor &color ) { KoColor minColor( color ); d->currentColor = minColor; d->colorChooser->blockSignals(true); d->colorChooser->slotSetColor(color); d->colorChooser->blockSignals(false); KoColor maxColor( color ); minColor.setOpacity( OPACITY_TRANSPARENT_U8 ); maxColor.setOpacity( OPACITY_OPAQUE_U8 ); d->opacitySlider->blockSignals( true ); d->opacitySlider->setColors( minColor, maxColor ); d->opacitySlider->setValue( color.opacityU8() ); d->opacitySlider->blockSignals( false ); updateIcon(); } void KoColorPopupAction::setCurrentColor( const QColor &_color ) { #ifndef NDEBUG if (!_color.isValid()) { warnWidgets << "Invalid color given, defaulting to black"; } #endif const QColor color(_color.isValid() ? _color : QColor(0,0,0,255)); setCurrentColor(KoColor(color, KoColorSpaceRegistry::instance()->rgb8() )); } QColor KoColorPopupAction::currentColor() const { return d->currentColor.toQColor(); } KoColor KoColorPopupAction::currentKoColor() const { return d->currentColor; } void KoColorPopupAction::updateIcon() { QSize iconSize; QToolButton *toolButton = dynamic_cast(parentWidget()); if (toolButton) { iconSize = QSize(toolButton->iconSize()); } else { iconSize = QSize(16, 16); } // This must be a QImage, as drawing to a QPixmap outside the // UI thread will cause sporadic crashes. QImage pm; if (icon().isNull()) { d->applyMode = false; } if(d->applyMode) { pm = icon().pixmap(iconSize).toImage(); if (pm.isNull()) { pm = QImage(iconSize, QImage::Format_ARGB32_Premultiplied); pm.fill(Qt::transparent); } QPainter p(&pm); p.fillRect(0, iconSize.height() - 4, iconSize.width(), 4, d->currentColor.toQColor()); p.end(); } else { pm = QImage(iconSize, QImage::Format_ARGB32_Premultiplied); pm.fill(Qt::transparent); QPainter p(&pm); d->checkerPainter.paint(p, QRect(QPoint(),iconSize)); p.fillRect(0, 0, iconSize.width(), iconSize.height(), d->currentColor.toQColor()); p.end(); } setIcon(QIcon(QPixmap::fromImage(pm))); } void KoColorPopupAction::emitColorChanged() { emit colorChanged( d->currentColor ); } void KoColorPopupAction::colorWasSelected(const KoColor &color, bool final) { d->currentColor = color; if (final) { menu()->hide(); emitColorChanged(); } updateIcon(); } void KoColorPopupAction::colorWasEdited( const QColor &color ) { d->currentColor = KoColor( color, KoColorSpaceRegistry::instance()->rgb8() ); quint8 opacity = d->opacitySlider->value(); d->currentColor.setOpacity( opacity ); KoColor minColor = d->currentColor; minColor.setOpacity( OPACITY_TRANSPARENT_U8 ); KoColor maxColor = minColor; maxColor.setOpacity( OPACITY_OPAQUE_U8 ); d->opacitySlider->setColors( minColor, maxColor ); emitColorChanged(); updateIcon(); } void KoColorPopupAction::opacityWasChanged( int opacity ) { d->currentColor.setOpacity( quint8(opacity) ); emitColorChanged(); } - -void KoColorPopupAction::slotTriggered(bool) -{ - if (d->firstTime) { - KoResourceServer* srv = KoResourceServerProvider::instance()->paletteServer(); - QList palettes = srv->resources(); - if (!palettes.empty()) { - d->colorSetWidget->setColorSet(palettes.first()); - } - d->firstTime = false; - } -} diff --git a/libs/widgets/KoColorPopupAction.h b/libs/widgets/KoColorPopupAction.h index f4a8832c92..7028c3ffb0 100644 --- a/libs/widgets/KoColorPopupAction.h +++ b/libs/widgets/KoColorPopupAction.h @@ -1,89 +1,88 @@ /* This file is part of the KDE project * Copyright (c) 2007 C. Boemann * Copyright (C) 2007 Fredy Yanardi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLORPOPUPACTION_H #define KOCOLORPOPUPACTION_H #include #include "kritawidgets_export.h" class KoColor; /** * KoColorPopupAction makes use of KoColorSetWidget to show a widget for for choosing a color (colormanaged via pigment). * @see KoColorPopupAction */ class KRITAWIDGETS_EXPORT KoColorPopupAction : public QAction { Q_OBJECT public: /** * Constructs a KoColorPopupAction with the specified parent. * * @param parent The parent for this action. */ explicit KoColorPopupAction(QObject *parent = 0); /** * Destructor */ ~KoColorPopupAction() override; public Q_SLOTS: /// Sets a new color to be displayed void setCurrentColor( const QColor &color ); /// Sets a new color to be displayed void setCurrentColor( const KoColor &color ); /// Returns the current color QColor currentColor() const; /// Returns the current color as a KoColor KoColor currentKoColor() const; /// update the icon - only needed if you resize the iconsize in the widget that shows the action void updateIcon(); Q_SIGNALS: /** * Emitted every time the color changes (by calling setColor() or * by user interaction. * @param color the new color */ void colorChanged(const KoColor &color); private Q_SLOTS: void emitColorChanged(); void colorWasSelected(const KoColor &color, bool final); void colorWasEdited( const QColor &color ); void opacityWasChanged( int opacity ); - void slotTriggered(bool); private: class KoColorPopupActionPrivate; KoColorPopupActionPrivate * const d; }; #endif diff --git a/libs/widgets/KoColorSetWidget.cpp b/libs/widgets/KoColorSetWidget.cpp index ab2f13acc2..1ad682e304 100644 --- a/libs/widgets/KoColorSetWidget.cpp +++ b/libs/widgets/KoColorSetWidget.cpp @@ -1,335 +1,217 @@ /* 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 +#include -void KoColorSetWidget::KoColorSetWidgetPrivate::fillColors() -{ - delete colorSetContainer; - colorSetContainer = new QWidget(); - colorSetLayout = new QVBoxLayout(); - colorSetLayout->setMargin(3); - colorSetLayout->setSpacing(0); // otherwise the use can click where there is none - colorSetContainer->setBackgroundRole(QPalette::Dark); - - int patchSize = 12; - - int columns = 16; - if (colorSet) { - columns = colorSet->columnCount(); - } - colorSetContainer->setMinimumWidth(columns*patchSize+6); - colorSetContainer->setLayout(colorSetLayout); - patchWidgetList.clear(); - colornames.clear(); - colorNameCmb->clear(); - - QWidget *defaultGroupContainer = new QWidget(); - QGridLayout *colorGroupLayout = new QGridLayout(); - for(int i = 0; isetColumnMinimumWidth(i, patchSize); - } - defaultGroupContainer->setMinimumWidth(columns*patchSize); - defaultGroupContainer->setMaximumWidth(columns*patchSize); - colorGroupLayout->setSpacing(0); - colorGroupLayout->setMargin(0); - defaultGroupContainer->setLayout(colorGroupLayout); - if (colorSet) { - for( quint32 i = 0, p= 0; i < colorSet->nColorsGroup(); i++) { - KoColorPatch *patch = new KoColorPatch(colorSetContainer); - patch->setFrameStyle(QFrame::Plain | QFrame::Box); - patch->setLineWidth(1); - KoColorSetEntry c = colorSet->getColorGlobal(i); - patch->setColor(c.color()); - patch->setToolTip(c.name()); - connect(patch, SIGNAL(triggered(KoColorPatch*)), thePublic, SLOT(colorTriggered(KoColorPatch*))); - colorGroupLayout->addWidget(patch, p/columns, p%columns); - patch->setDisplayRenderer(displayRenderer); - patchWidgetList.append(patch); - colornames.append(c.name()); - QPixmap colorsquare = QPixmap(12,12); - colorsquare.fill(c.color().toQColor()); - colorNameCmb->addItem(QIcon(colorsquare), c.name()); - ++p; - } - colorSetLayout->addWidget(defaultGroupContainer); - Q_FOREACH(QString groupName, colorSet->getGroupNames()) { - QGroupBox *groupbox = new QGroupBox(); - groupbox->setTitle(groupName); - QGridLayout *groupLayout = new QGridLayout(); - for(int i = 0; isetColumnMinimumWidth(i, patchSize); - } - groupLayout->setSpacing(0); - groupLayout->setMargin(0); - groupbox->setMinimumWidth(columns*patchSize); - groupbox->setMaximumWidth(columns*patchSize); - groupbox->setLayout(groupLayout); - groupbox->setFlat(true); - for( quint32 i = 0, p= 0; i < colorSet->nColorsGroup(groupName); i++) { - KoColorPatch *patch = new KoColorPatch(colorSetContainer); - patch->setFrameStyle(QFrame::Plain | QFrame::Box); - patch->setLineWidth(1); - KoColorSetEntry c = colorSet->getColorGroup(i, groupName); - patch->setColor(c.color()); - patch->setToolTip(c.name()); - connect(patch, SIGNAL(triggered(KoColorPatch*)), thePublic, SLOT(colorTriggered(KoColorPatch*))); - groupLayout->addWidget(patch, p/columns, p%columns); - patch->setDisplayRenderer(displayRenderer); - patchWidgetList.append(patch); - colornames.append(c.name()); - QPixmap colorsquare = QPixmap(12,12); - colorsquare.fill(c.color().toQColor()); - colorNameCmb->addItem(QIcon(colorsquare), c.name()); - ++p; - } - colorSetLayout->addWidget(groupbox); - } - } - - scrollArea->setWidget(colorSetContainer); - connect(colorNameCmb, SIGNAL(activated(QString)), thePublic, SLOT(setColorFromString(QString)), Qt::UniqueConnection); -} - -void KoColorSetWidget::KoColorSetWidgetPrivate::addRemoveColors() -{ - KoResourceServer* srv = KoResourceServerProvider::instance()->paletteServer(); - QList palettes = srv->resources(); - - Q_ASSERT(colorSet); - KoEditColorSetDialog *dlg = new KoEditColorSetDialog(palettes, colorSet->name(), thePublic); - if (dlg->exec() == KoDialog::Accepted ) { // always reload the color set - KoColorSet * cs = dlg->activeColorSet(); - // check if the selected colorset is predefined - if( cs && !palettes.contains( cs ) ) { - int i = 1; - QFileInfo fileInfo; - QString savePath = srv->saveLocation(); - - do { - fileInfo.setFile(savePath + QString("%1.%2").arg(i++, 4, 10, QChar('0')).arg(colorSet->defaultFileExtension())); - } - while (fileInfo.exists()); - - cs->setFilename( fileInfo.filePath() ); - cs->setValid( true ); - - // add new colorset to predefined colorsets - if (!srv->addResource(cs)) { - - delete cs; - cs = 0; - } - } - if (cs) { - thePublic->setColorSet(cs); - } - } - delete dlg; -} +#include +#include +#include +#include void KoColorSetWidget::KoColorSetWidgetPrivate::addRecent(const KoColor &color) { - if(numRecents<6) { + if(numRecents < 6) { recentPatches[numRecents] = new KoColorPatch(thePublic); recentPatches[numRecents]->setFrameShape(QFrame::StyledPanel); recentPatches[numRecents]->setDisplayRenderer(displayRenderer); - recentsLayout->insertWidget(numRecents+1, recentPatches[numRecents]); - connect(recentPatches[numRecents], SIGNAL(triggered(KoColorPatch*)), thePublic, SLOT(colorTriggered(KoColorPatch*))); + 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(new KoColorSetWidgetPrivate()) { d->thePublic = this; - d->colorSet = 0; - - d->firstShowOfContainer = true; - - d->mainLayout = new QVBoxLayout(); - d->mainLayout->setMargin(4); - d->mainLayout->setSpacing(2); - - d->colorSetContainer = 0; d->numRecents = 0; - d->recentsLayout = new QHBoxLayout(); - d->mainLayout->addLayout(d->recentsLayout); + d->recentsLayout = 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->scrollArea = new QScrollArea(); - d->scrollArea->setBackgroundRole(QPalette::Dark); - d->mainLayout->addWidget(d->scrollArea); - d->colorNameCmb = new QComboBox(this); - d->colorNameCmb->setEditable(true); - d->colorNameCmb->setInsertPolicy(QComboBox::NoInsert); - d->mainLayout->addWidget(d->colorNameCmb); - d->fillColors(); + d->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->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->addRemoveButton = new QToolButton(this); - d->addRemoveButton->setText(i18n("Add / Remove Colors...")); - d->addRemoveButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - connect(d->addRemoveButton, SIGNAL(clicked()), SLOT(addRemoveColors())); - d->mainLayout->addWidget(d->addRemoveButton); + 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); - KoColorSet *colorSet = new KoColorSet(); - d->colorSet = colorSet; - d->fillColors(); + connect(d->paletteChooser, SIGNAL(sigPaletteSelected(KoColorSet*)), + SLOT(slotPaletteChoosen(KoColorSet*))); + connect(d->paletteView, SIGNAL(sigColorSelected(KoColor)), + SLOT(slotColorSelectedByPalette(KoColor))); + connect(d->colorNameCmb, SIGNAL(sigColorSelected(KoColor)), + SLOT(slotNameListSelection(KoColor))); + + d->rServer = KoResourceServerProvider::instance()->paletteServer(); + QPointer defaultColorSet = d->rServer->resourceByName("Default"); + if (!defaultColorSet && d->rServer->resources().count() > 0) { + defaultColorSet = d->rServer->resources().first(); + } + setColorSet(defaultColorSet); } KoColorSetWidget::~KoColorSetWidget() { - KoResourceServer* srv = KoResourceServerProvider::instance()->paletteServer(); - QList palettes = srv->resources(); - if (!palettes.contains(d->colorSet)) { - delete d->colorSet; - } delete d; } -void KoColorSetWidget::KoColorSetWidgetPrivate::colorTriggered(KoColorPatch *patch) -{ - int i; - - emit thePublic->colorChanged(patch->color(), true); - - colorNameCmb->setCurrentIndex(colornames.indexOf(QRegExp(patch->toolTip()+"|Fixed"))); - - for (i = 0; i color()); -} - -void KoColorSetWidget::KoColorSetWidgetPrivate::setColorFromString(QString s) -{ - int i = colornames.indexOf(QRegExp(s+"|Fixed")); - i = qMax(i,0); - colorTriggered(patchWidgetList.at(i)); -} - void KoColorSetWidget::setColorSet(QPointer colorSet) { if (!colorSet) return; + if (colorSet == d->colorSet) return; - if (colorSet != d->colorSet) { - KoResourceServer* srv = KoResourceServerProvider::instance()->paletteServer(); - QList palettes = srv->resources(); - if (!palettes.contains(d->colorSet)) { - delete d->colorSet; - } - - d->colorSet = colorSet; - } - - d->fillColors(); + d->paletteView->paletteModel()->setPalette(colorSet.data()); + d->colorSet = colorSet; } KoColorSet* KoColorSetWidget::colorSet() { return d->colorSet; } void KoColorSetWidget::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { d->displayRenderer = displayRenderer; - Q_FOREACH(KoColorPatch *p, d->patchWidgetList) { - p->setDisplayRenderer(displayRenderer); - } for (int i=0; i<6; i++) { if (d->recentPatches[i]) { d->recentPatches[i]->setDisplayRenderer(displayRenderer); } } } } void KoColorSetWidget::resizeEvent(QResizeEvent *event) { emit widgetSizeChanged(event->size()); QFrame::resizeEvent(event); } +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(KoColorSet *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.h b/libs/widgets/KoColorSetWidget.h index a9fc3fe9ad..fa920f5dad 100644 --- a/libs/widgets/KoColorSetWidget.h +++ b/libs/widgets/KoColorSetWidget.h @@ -1,107 +1,120 @@ /* This file is part of the KDE project Copyright (c) 2007 C. Boemann Copyright (c) 2007 Fredy Yanardi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLORSETWIDGET_H_ #define KOCOLORSETWIDGET_H_ #include #include -#include "kritawidgets_export.h" +#include #include +#include "kritawidgets_export.h" + class KoColor; class KoColorSet; +class KoColorPatch; /** * @short A colormanaged widget for choosing a color from a colorset * * KoColorSetWidget is a widget for choosing a color (colormanaged via pigment). It shows a color * set plus optionally a checkbox to filter away bad matching colors. */ class KRITAWIDGETS_EXPORT KoColorSetWidget : public QFrame { Q_OBJECT public: /** * Constructor for the widget, where color is initially blackpoint of sRGB * * @param parent parent QWidget */ explicit KoColorSetWidget(QWidget *parent=0); /** * Destructor */ ~KoColorSetWidget() override; /** * Sets the color set that this widget shows. * @param colorSet pointer to the color set */ void setColorSet(QPointer colorSet); /** * @brief setDisplayRenderer * Set the display renderer of this object. * @param displayRenderer */ void setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer); /** * Gets the current color set * @returns current color set,, 0 if none set */ KoColorSet* colorSet(); protected: void resizeEvent(QResizeEvent *event) override; ///< reimplemented from QFrame Q_SIGNALS: /** * Emitted every time the color changes (by calling setColor() or * by user interaction. * @param color the new color * @param final if the value is final (ie not produced by the pointer moving over around) */ void colorChanged(const KoColor &color, bool final); /** * Emitted every time the size of this widget changes because of new colorset with * different number of colors is loaded. This is useful for KoColorSetAction to update * correct size of the menu showing this widget. * @param size the new size */ void widgetSizeChanged(const QSize &size); -private: - Q_PRIVATE_SLOT(d, void colorTriggered(KoColorPatch *)) - Q_PRIVATE_SLOT(d, void addRemoveColors()) - Q_PRIVATE_SLOT(d, void setColorFromString(QString s)) +private Q_SLOTS: + /** + * @brief slotPatchTriggered + * Triggered when a recent patch is triggered + */ + void slotPatchTriggered(KoColorPatch *); + /** + * @brief slotEntrySelected + * Triggered when a color is choose from the palette view + */ + void slotColorSelectedByPalette(const KoColor &color); + void slotPaletteChoosen(KoColorSet *); + void slotNameListSelection(const KoColor &); +private: class KoColorSetWidgetPrivate; KoColorSetWidgetPrivate * const d; }; #endif diff --git a/libs/widgets/KoColorSetWidget_p.h b/libs/widgets/KoColorSetWidget_p.h index 1fae481b7f..4c38123401 100644 --- a/libs/widgets/KoColorSetWidget_p.h +++ b/libs/widgets/KoColorSetWidget_p.h @@ -1,78 +1,81 @@ /* 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; QPointer colorSet; - QTimer m_timer; + + KisPaletteView *paletteView; + KisPaletteListWidget *paletteChooser; + KisPopupButton *paletteChooserButton; + QVBoxLayout *mainLayout; - bool firstShowOfContainer; - QWidget *colorSetContainer; - QScrollArea *scrollArea; QVBoxLayout *colorSetLayout; QHBoxLayout *recentsLayout; + QHBoxLayout *bottomLayout; + KoColorPatch *recentPatches[6]; QToolButton *addRemoveButton; - QComboBox *colorNameCmb; - QStringList colornames; + KisPaletteComboBox *colorNameCmb; int numRecents; - void colorTriggered(KoColorPatch *patch); + const KoColorDisplayRendererInterface *displayRenderer; + KoResourceServer *rServer; + void addRecent(const KoColor &); void activateRecent(int i); - void fillColors(); void addRemoveColors(); - void setColorFromString(QString s); - - QList patchWidgetList; - const KoColorDisplayRendererInterface *displayRenderer; - }; #endif diff --git a/libs/widgets/KoEditColorSet.ui b/libs/widgets/KoEditColorSet.ui deleted file mode 100644 index c57d2d5478..0000000000 --- a/libs/widgets/KoEditColorSet.ui +++ /dev/null @@ -1,88 +0,0 @@ - - KoEditColorSet - - - - 0 - 0 - 173 - 106 - - - - - - - true - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - - - - - - - QFrame::Box - - - QFrame::Sunken - - - - - - - Add - - - ... - - - - - - - Remove - - - ... - - - - - - - Open palette - - - ... - - - - - - - Save palette - - - ... - - - - - - - - - - diff --git a/libs/widgets/KoEditColorSetDialog.h b/libs/widgets/KoEditColorSetDialog.h deleted file mode 100644 index 7e2066c5e5..0000000000 --- a/libs/widgets/KoEditColorSetDialog.h +++ /dev/null @@ -1,105 +0,0 @@ -/* This file is part of the KDE project - * Copyright (C) 2007 Fredy Yanardi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef KOEDITCOLORSET_H -#define KOEDITCOLORSET_H - -#include - -#include - -#include "kritawidgets_export.h" - -class QGridLayout; -class QScrollArea; -class KoColorPatch; -class KoColorSet; - -class KoEditColorSetWidget : public QWidget -{ - Q_OBJECT -public: - KoEditColorSetWidget(const QList &palettes, const QString &activePalette, QWidget *parent = 0); - ~KoEditColorSetWidget() override; - - /** - * Return the active color set. The caller takes ownership of that color set. - */ - KoColorSet *activeColorSet(); - -private Q_SLOTS: - void setActiveColorSet(int index); - void setTextLabel(KoColorPatch *patch); - void addColor(); - void removeColor(); - void open(); - void save(); - -private: - Ui::KoEditColorSet widget; - QList m_colorSets; - QGridLayout *m_gridLayout; - QScrollArea *m_scrollArea; - KoColorSet *m_activeColorSet; - KoColorPatch *m_activePatch; - uint m_initialColorSetCount; - bool m_activeColorSetRequested; -}; - -/** - * A dialog for editing palettes/color sets in an application. Example use of this dialog is in text color toolbar, - * the toolbar brings a set of colors from one palette, and a button brings this dialog for editing palettes. - * This dialog is able to: - * - Set active palette from a combobox - * - Add/remove color from a palette - * - Open new palette from a gimp palette file (.gpl) - * - Save changes to the file - * @see KoColorSetWidget - */ -class KRITAWIDGETS_EXPORT KoEditColorSetDialog : public KoDialog -{ - Q_OBJECT - -public: - /** - * Constructs a KoEditColorSetDialog. - * @param palettes all available palettes that are going to be edited. - * @param activePalette name of the palette which will be activated after this dialog is shown. - * @param parent the parent widget - */ - KoEditColorSetDialog(const QList &palettes, const QString &activePalette, QWidget *parent = 0); - - /** - * Returns the last active color set. - * The caller takes ownership of that color set. - * @return the last active KoColorSet in the dialog before the user press OK - */ - KoColorSet *activeColorSet(); - - /** - * Destructor - */ - ~KoEditColorSetDialog() override; - -private: - KoEditColorSetWidget *ui; -}; - -#endif - diff --git a/libs/widgets/WdgDlgInternalColorSelector.ui b/libs/widgets/WdgDlgInternalColorSelector.ui index b2c43046d5..2e56498ffc 100644 --- a/libs/widgets/WdgDlgInternalColorSelector.ui +++ b/libs/widgets/WdgDlgInternalColorSelector.ui @@ -1,279 +1,279 @@ WdgDlgInternalColorSelector 0 0 505 483 Dialog 0 0 90 90 0 0 0 50 QFrame::StyledPanel QFrame::Sunken 2 2 0 0 0 0 0 0 0 50 70 0 0 25 25 0 0 90 0 0 - + 0 0 50 0 - + 0 0 true 0 0 50 50 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok KisPopupButton QPushButton
kis_popup_button.h
KisPaletteView QTableView
kis_palette_view.h
- - SqueezedComboBox - QComboBox -
squeezedcombobox.h
-
KisSpinboxColorSelector QWidget
kis_spinbox_color_selector.h
1
KisVisualColorSelector QWidget
KisVisualColorSelector.h
1
KoColorPatch QWidget
KoColorPatch.h
1
+ + KisPaletteComboBox + QComboBox +
KisPaletteComboBox.h
+
buttonBox accepted() WdgDlgInternalColorSelector accept() 248 254 157 274 buttonBox rejected() WdgDlgInternalColorSelector reject() 316 260 286 274
diff --git a/libs/widgets/WdgPaletteListWidget.ui b/libs/widgets/WdgPaletteListWidget.ui new file mode 100644 index 0000000000..ccc28a22f6 --- /dev/null +++ b/libs/widgets/WdgPaletteListWidget.ui @@ -0,0 +1,92 @@ + + + WdgPaletteListWidget + + + + 0 + 0 + 361 + 496 + + + + Form + + + + + + + + + 0 + 0 + + + + + + + + + + + 0 + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + .. + + + + + + + + .. + + + + + + + + .. + + + + + + + + .. + + + + + + + + + + + diff --git a/libs/widgets/kis_palette_view.cpp b/libs/widgets/kis_palette_view.cpp index 065e4fb70a..134b56f003 100644 --- a/libs/widgets/kis_palette_view.cpp +++ b/libs/widgets/kis_palette_view.cpp @@ -1,345 +1,281 @@ /* * 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 "kis_palette_delegate.h" +#include "KisPaletteDelegate.h" #include "KisPaletteModel.h" #include "kis_color_button.h" +#include + +int KisPaletteView::MININUM_ROW_HEIGHT = 10; struct KisPaletteView::Private { - KisPaletteModel *model = nullptr; - bool allowPaletteModification = true; + QPointer model; + bool allowPaletteModification; // if modification is allowed from this widget }; KisPaletteView::KisPaletteView(QWidget *parent) - : KoTableView(parent) + : QTableView(parent) , m_d(new Private) { - setShowGrid(false); - horizontalHeader()->setVisible(false); - verticalHeader()->setVisible(false); - setItemDelegate(new KisPaletteDelegate()); - -// setDragEnabled(true); -// setDragDropMode(QAbstractItemView::InternalMove); - setDropIndicatorShown(true); + m_d->allowPaletteModification = false; - KConfigGroup cfg(KSharedConfig::openConfig()->group("")); - //QPalette pal(palette()); - //pal.setColor(QPalette::Base, cfg.getMDIBackgroundColor()); - //setAutoFillBackground(true); - //setPalette(pal); + setItemDelegate(new KisPaletteDelegate(this)); - int defaultSectionSize = cfg.readEntry("paletteDockerPaletteViewSectionSize", 12); - horizontalHeader()->setDefaultSectionSize(defaultSectionSize); - verticalHeader()->setDefaultSectionSize(defaultSectionSize); - connect(this, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(modifyEntry(QModelIndex))); + setShowGrid(true); + setDropIndicatorShown(true); + setDragDropMode(QAbstractItemView::InternalMove); + setSelectionMode(QAbstractItemView::SingleSelection); + setDragEnabled(false); + setAcceptDrops(false); + + /* + * without this, a cycle might be created: + * the view streches 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); } 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) { - KoDialog *window = new KoDialog(this); + QScopedPointer window(new KoDialog(this)); window->setWindowTitle(i18nc("@title:window", "Add a new Colorset Entry")); - QFormLayout *editableItems = new QFormLayout(window); + QFormLayout *editableItems = new QFormLayout(window.data()); window->mainWidget()->setLayout(editableItems); - QComboBox *cmbGroups = new QComboBox(window); + 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); - QLineEdit *lnName = new QLineEdit(window); - KisColorButton *bnColor = new KisColorButton(window); - QCheckBox *chkSpot = new QCheckBox(window); + 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()->nColors()+1)); - lnIDName->setText(QString::number(m_d->model->colorSet()->nColors()+1)); + 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(); } - KoColorSetEntry newEntry; + KisSwatch newEntry; newEntry.setColor(bnColor->color()); newEntry.setName(lnName->text()); newEntry.setId(lnIDName->text()); newEntry.setSpotColor(chkSpot->isChecked()); - m_d->model->addColorSetEntry(newEntry, groupName); - m_d->model->colorSet()->save(); + m_d->model->addEntry(newEntry, groupName); return true; } + return false; } -// should be move to colorSetChooser 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) { - QString groupName = lnName->text(); - m_d->model->addGroup(groupName); + KisSwatchGroup group; + group.setName(lnName->text()); + m_d->model->addGroup(group); m_d->model->colorSet()->save(); return true; } return false; } bool KisPaletteView::removeEntryWithDialog(QModelIndex index) { - bool keepColors = true; - if (qvariant_cast(index.data(KisPaletteModel::IsHeaderRole))) { - KoDialog *window = new KoDialog(); + 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(); - QCheckBox *chkKeep = new QCheckBox(); + QFormLayout *editableItems = new QFormLayout(window.data()); + QCheckBox *chkKeep = new QCheckBox(window.data()); window->mainWidget()->setLayout(editableItems); - editableItems->addRow(i18nc("Shows up when deleting a group","Keep the Colors"), chkKeep); - chkKeep->setChecked(keepColors); - if (window->exec() == KoDialog::Accepted) { - keepColors = chkKeep->isChecked(); - m_d->model->removeEntry(index, keepColors); - m_d->model->colorSet()->save(); - } - } else { - m_d->model->removeEntry(index, keepColors); + 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(); } - - this->paletteModelChanged(); // refresh the UI with new model data - return true; } -void KisPaletteView::trySelectClosestColor(KoColor color) +void KisPaletteView::selectClosestColor(const KoColor &color) { KoColorSet* color_set = m_d->model->colorSet(); - if (!color_set) + if (!color_set) { return; + } //also don't select if the color is the same as the current selection - if (selectedIndexes().size()>0) { - QModelIndex currentI = currentIndex(); - if (!currentI.isValid()) { - currentI = selectedIndexes().last(); - } - if (!currentI.isValid()) { - currentI = selectedIndexes().first(); - } - if (currentI.isValid()) { - if (m_d->model->colorSetEntryFromIndex(currentI).color()==color) { - return; - } - } + if (m_d->model->getEntry(currentIndex()).color() == color) { + return; } - quint32 i = color_set->getIndexClosestColor(color); - QModelIndex index = m_d->model->indexFromId(i); - this->selectionModel()->clearSelection(); - this->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select); -} -void KisPaletteView::mouseReleaseEvent(QMouseEvent *event) -{ - bool foreground = false; - if (event->button()== Qt::LeftButton) { - foreground = true; - } - entrySelection(foreground); + selectionModel()->clearSelection(); + QModelIndex index = m_d->model->indexForClosest(color); + selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select); } -void KisPaletteView::paletteModelChanged() +void KisPaletteView::slotFGColorChanged(const KoColor &color) { - updateView(); - updateRows(); + selectClosestColor(color); } void KisPaletteView::setPaletteModel(KisPaletteModel *model) { if (m_d->model) { - disconnect(m_d->model, 0, this, 0); + disconnect(m_d->model, Q_NULLPTR, this, Q_NULLPTR); } m_d->model = model; setModel(model); - paletteModelChanged(); - connect(m_d->model, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), this, SLOT(paletteModelChanged())); - connect(m_d->model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(paletteModelChanged())); - connect(m_d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(paletteModelChanged())); - connect(m_d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(paletteModelChanged())); - connect(m_d->model, SIGNAL(modelReset()), this, SLOT(paletteModelChanged())); - + slotAdditionalGuiUpdate(); + connect(model, SIGNAL(sigPaletteModified()), + SLOT(slotAdditionalGuiUpdate())); + connect(model, SIGNAL(sigPaletteChanged()), + SLOT(slotAdditionalGuiUpdate())); + + connect(selectionModel(), SIGNAL(currentChanged(QModelIndex, QModelIndex)), + SLOT(slotCurrentSelectionChanged(QModelIndex))); } KisPaletteModel* KisPaletteView::paletteModel() const { return m_d->model; } -void KisPaletteView::updateRows() +void KisPaletteView::setAllowModification(bool allow) { - this->clearSpans(); - if (m_d->model) { - for (int r=0; r<=m_d->model->rowCount(); r++) { - QModelIndex index = m_d->model->index(r, 0); - if (qvariant_cast(index.data(KisPaletteModel::IsHeaderRole))) { - setSpan(r, 0, 1, m_d->model->columnCount()); - setRowHeight(r, this->fontMetrics().lineSpacing()+6); - } else { - this->setRowHeight(r, this->columnWidth(0)); - } - } - } + m_d->allowPaletteModification = allow; + setDragEnabled(allow); + setAcceptDrops(allow); } -void KisPaletteView::setAllowModification(bool allow) +void KisPaletteView::slotHorizontalHeaderResized(int, int, int newSize) { - m_d->allowPaletteModification = allow; + resizeRows(newSize); + slotAdditionalGuiUpdate(); } +void KisPaletteView::resizeRows(int newSize) +{ + verticalHeader()->setDefaultSectionSize(newSize); + verticalHeader()->resizeSections(QHeaderView::Fixed); +} -void KisPaletteView::wheelEvent(QWheelEvent *event) +void KisPaletteView::removeSelectedEntry() { - if (event->modifiers() & Qt::ControlModifier) { - int numDegrees = event->delta() / 8; - int numSteps = numDegrees / 7; - int curSize = horizontalHeader()->sectionSize(0); - int setSize = numSteps + curSize; - - if ( (event->delta() <= 0) && (setSize <= 8) ) { - // Ignore scroll-zooming down below a certain size - } else { - horizontalHeader()->setDefaultSectionSize(setSize); - verticalHeader()->setDefaultSectionSize(setSize); - KConfigGroup cfg(KSharedConfig::openConfig()->group("")); - cfg.writeEntry("paletteDockerPaletteViewSectionSize", setSize); - } - - event->accept(); - } else { - KoTableView::wheelEvent(event); + if (selectedIndexes().size() <= 0) { + return; } + m_d->model->removeEntry(currentIndex()); } -void KisPaletteView::entrySelection(bool foreground) { - QModelIndex index; - if (selectedIndexes().size()<=0) { - return; +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); } - if (selectedIndexes().last().isValid()) { - index = selectedIndexes().last(); - } else if (selectedIndexes().first().isValid()) { - index = selectedIndexes().first(); - } else { +} + +void KisPaletteView::slotCurrentSelectionChanged(const QModelIndex &newCurrent) +{ + if (!newCurrent.isValid()) { return; } + + emit sigIndexSelected(newCurrent); + if (qvariant_cast(newCurrent.data(KisPaletteModel::IsGroupNameRole))) { return; } - if (qvariant_cast(index.data(KisPaletteModel::IsHeaderRole))==false) { - KoColorSetEntry entry = m_d->model->colorSetEntryFromIndex(index); - if (foreground) { - emit(entrySelected(entry)); - emit(indexEntrySelected(index)); - } else { - emit(entrySelectedBackGround(entry)); - emit(indexEntrySelected(index)); - } + if (qvariant_cast(newCurrent.data(KisPaletteModel::CheckSlotRole))) { + KisSwatch entry = m_d->model->getEntry(newCurrent); + emit sigColorSelected(entry.color()); } } -void KisPaletteView::modifyEntry(QModelIndex index) { - if (m_d->allowPaletteModification) { - KoDialog *group = new KoDialog(this); - QFormLayout *editableItems = new QFormLayout(group); - group->mainWidget()->setLayout(editableItems); - QLineEdit *lnIDName = new QLineEdit(group); - QLineEdit *lnGroupName = new QLineEdit(group); - KisColorButton *bnColor = new KisColorButton(group); - QCheckBox *chkSpot = new QCheckBox(group); - - if (qvariant_cast(index.data(KisPaletteModel::IsHeaderRole))) { - QString groupName = qvariant_cast(index.data(Qt::DisplayRole)); - editableItems->addRow(i18nc("Name for a colorgroup","Name"), lnGroupName); - lnGroupName->setText(groupName); - if (group->exec() == KoDialog::Accepted) { - m_d->model->colorSet()->changeGroupName(groupName, lnGroupName->text()); - m_d->model->colorSet()->save(); - updateRows(); - } - //rename the group. - } else { - KoColorSetEntry entry = m_d->model->colorSetEntryFromIndex(index); - QStringList entryList = qvariant_cast(index.data(KisPaletteModel::RetrieveEntryRole)); - 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("ID"), lnIDName); - editableItems->addRow(i18n("Name"), lnGroupName); - editableItems->addRow(i18n("Color"), bnColor); - editableItems->addRow(i18n("Spot"), chkSpot); - lnGroupName->setText(entry.name()); - lnIDName->setText(entry.id()); - bnColor->setColor(entry.color()); - chkSpot->setChecked(entry.spotColor()); - if (group->exec() == KoDialog::Accepted) { - entry.setName(lnGroupName->text()); - entry.setId(lnIDName->text()); - entry.setColor(bnColor->color()); - entry.setSpotColor(chkSpot->isChecked()); - m_d->model->colorSet()->changeColorSetEntry(entry, entryList.at(0), entryList.at(1).toUInt()); - m_d->model->colorSet()->save(); - } - } - } +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 fa8795289e..3098bacff4 100644 --- a/libs/widgets/kis_palette_view.h +++ b/libs/widgets/kis_palette_view.h @@ -1,119 +1,118 @@ /* * 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 #include "kritawidgets_export.h" class KisPaletteModel; class QWheelEvent; +class KoColorDisplayRendererInterface; - -class KRITAWIDGETS_EXPORT KisPaletteView : public KoTableView +class KRITAWIDGETS_EXPORT KisPaletteView : public QTableView { Q_OBJECT +private: + static int MININUM_ROW_HEIGHT; public: - KisPaletteView(QWidget *parent = 0); + explicit KisPaletteView(QWidget *parent = Q_NULLPTR); ~KisPaletteView() override; void setPaletteModel(KisPaletteModel *model); KisPaletteModel* paletteModel() const; - /** - * @brief updateRows - * update the rows so they have the proper columnspanning. - */ - void updateRows(); +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); -public Q_SLOTS: - void paletteModelChanged(); /** * add an entry with a dialog window. + * @warning deprecated. + * kept for compatibility with @ref PaletteView in @ref libkis */ bool addEntryWithDialog(KoColor color); - /** - * @brief addGroupWithDialog - * summons a little dialog to name the new group. - */ - bool addGroupWithDialog(); /** * remove entry with a dialog window.(Necessary for groups. + * @warning deprecated. + * kept for compatibility with @ref PaletteView in @ref libkis */ bool removeEntryWithDialog(QModelIndex index); /** - * This tries to select the closest color in the palette. - * This doesn't update the foreground color, just the visual selection. + * add entry with a dialog window. + * @warning deprecated. + * kept for compatibility with @ref PaletteView in @ref libkis */ - void trySelectClosestColor(KoColor color); + bool addGroupWithDialog(); + Q_SIGNALS: + void sigIndexSelected(const QModelIndex &index); + void sigColorSelected(const KoColor &); + +public Q_SLOTS: /** - * @brief entrySelected - * signals when an entry is selected. - * @param entry the selected entry. + * This tries to select the closest color in the palette. + * This doesn't update the foreground color, just the visual selection. */ - void entrySelected(KoColorSetEntry entry); - void entrySelectedBackGround(KoColorSetEntry entry); - void indexEntrySelected(QModelIndex index); -protected: - void mouseReleaseEvent(QMouseEvent *event) override; - void wheelEvent(QWheelEvent *event) override; + void slotFGColorChanged(const KoColor &); + +private Q_SLOTS: + void slotHorizontalHeaderResized(int, int, int newSize); + void slotAdditionalGuiUpdate(); + void slotCurrentSelectionChanged(const QModelIndex &newCurrent); + +private: + void resizeRows(int newSize); private: struct Private; const QScopedPointer m_d; -private Q_SLOTS: - /** - * @brief entrySelection - * the function that will emit entrySelected when the entry changes. - */ - void entrySelection(bool foreground = true); - /** - * @brief modifyEntry - * function for changing the entry at the given index. - * if modification isn't allow(@see setAllowModification), this does nothing. - */ - void modifyEntry(QModelIndex index); }; #endif /* __KIS_PALETTE_VIEW_H */ diff --git a/plugins/dockers/palettedocker/CMakeLists.txt b/plugins/dockers/palettedocker/CMakeLists.txt index f665f8af2b..04b61a37f8 100644 --- a/plugins/dockers/palettedocker/CMakeLists.txt +++ b/plugins/dockers/palettedocker/CMakeLists.txt @@ -1,9 +1,11 @@ -set(KRITA_PALETTEDOCKER_SOURCES palettedocker.cpp palettedocker_dock.cpp ) +set(KRITA_PALETTEDOCKER_SOURCES + palettedocker.cpp palettedocker_dock.cpp + ) ki18n_wrap_ui(KRITA_PALETTEDOCKER_SOURCES wdgpalettedock.ui ) add_library(kritapalettedocker MODULE ${KRITA_PALETTEDOCKER_SOURCES}) target_link_libraries(kritapalettedocker kritaui) install(TARGETS kritapalettedocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/dockers/palettedocker/palettedocker_dock.cpp b/plugins/dockers/palettedocker/palettedocker_dock.cpp index 7ee7228c6c..441af29974 100644 --- a/plugins/dockers/palettedocker/palettedocker_dock.cpp +++ b/plugins/dockers/palettedocker/palettedocker_dock.cpp @@ -1,308 +1,369 @@ /* * 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 +#include +#include + +#include +#include -#include "KisPaletteModel.h" #include "ui_wdgpalettedock.h" -#include "kis_palette_delegate.h" -#include "kis_palette_view.h" -#include PaletteDockerDock::PaletteDockerDock( ) : QDockWidget(i18n("Palette")) - , m_wdgPaletteDock(new Ui_WdgPaletteDock()) - , m_currentColorSet(0) - , m_resourceProvider(0) - , m_canvas(0) + , m_ui(new Ui_WdgPaletteDock()) + , m_model(new KisPaletteModel(this)) + , m_paletteChooser(new KisPaletteListWidget(this)) + , m_view(Q_NULLPTR) + , m_resourceProvider(Q_NULLPTR) + , m_rServer(KoResourceServerProvider::instance()->paletteServer()) + , m_activeDocument(Q_NULLPTR) + , 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"))) { - QWidget* mainWidget = new QWidget(this); + QWidget *mainWidget = new QWidget(this); setWidget(mainWidget); - m_wdgPaletteDock->setupUi(mainWidget); - m_wdgPaletteDock->bnAdd->setIcon(KisIconUtils::loadIcon("list-add")); - m_wdgPaletteDock->bnAdd->setIconSize(QSize(16, 16)); - m_wdgPaletteDock->bnRemove->setIcon(KisIconUtils::loadIcon("edit-delete")); - m_wdgPaletteDock->bnRemove->setIconSize(QSize(16, 16)); - m_wdgPaletteDock->bnAdd->setEnabled(false); - m_wdgPaletteDock->bnRemove->setEnabled(false); - m_wdgPaletteDock->bnAddDialog->setVisible(false); - m_wdgPaletteDock->bnAddGroup->setIcon(KisIconUtils::loadIcon("groupLayer")); - m_wdgPaletteDock->bnAddGroup->setIconSize(QSize(16, 16)); + 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))); + + 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(KoColorSet*)), SLOT(slotSetColorSet(KoColorSet*))); + connect(m_paletteChooser, SIGNAL(sigAddPalette()), SLOT(slotAddPalette())); + connect(m_paletteChooser, SIGNAL(sigImportPalette()), SLOT(slotImportPalette())); + connect(m_paletteChooser, SIGNAL(sigRemovePalette(KoColorSet*)), SLOT(slotRemovePalette(KoColorSet*))); + connect(m_paletteChooser, SIGNAL(sigExportPalette(KoColorSet*)), SLOT(slotExportPalette(KoColorSet*))); + + m_ui->bnColorSets->setIcon(KisIconUtils::loadIcon("hi16-palette_library")); + m_ui->bnColorSets->setToolTip(i18n("Choose palette")); + m_ui->bnColorSets->setPopupWidget(m_paletteChooser); - - m_model = new KisPaletteModel(this); - m_wdgPaletteDock->paletteView->setPaletteModel(m_model); - - connect(m_wdgPaletteDock->bnAdd, SIGNAL(clicked(bool)), this, SLOT(addColorForeground())); - connect(m_wdgPaletteDock->bnRemove, SIGNAL(clicked(bool)), this, SLOT(removeColor())); - connect(m_wdgPaletteDock->bnAddGroup, SIGNAL(clicked(bool)), m_wdgPaletteDock->paletteView, SLOT(addGroupWithDialog())); - - connect(m_wdgPaletteDock->paletteView, SIGNAL(entrySelected(KoColorSetEntry)), this, SLOT(entrySelected(KoColorSetEntry))); - connect(m_wdgPaletteDock->paletteView, SIGNAL(entrySelectedBackGround(KoColorSetEntry)), this, SLOT(entrySelectedBack(KoColorSetEntry))); - - KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); - m_serverAdapter = QSharedPointer(new KoResourceServerAdapter(rServer)); - m_serverAdapter->connectToResourceServer(); - rServer->addObserver(this); - - m_paletteChooser = new KisColorsetChooser(this); - connect(m_paletteChooser, SIGNAL(paletteSelected(KoColorSet*)), this, SLOT(setColorSet(KoColorSet*))); - - m_wdgPaletteDock->bnColorSets->setIcon(KisIconUtils::loadIcon("hi16-palette_library")); - m_wdgPaletteDock->bnColorSets->setToolTip(i18n("Choose palette")); - m_wdgPaletteDock->bnColorSets->setPopupWidget(m_paletteChooser); - - connect(m_wdgPaletteDock->cmbNameList, SIGNAL(currentIndexChanged(int)), this, SLOT(setColorFromNameList(int))); KisConfig cfg(true); - QString defaultPalette = cfg.defaultPalette(); - KoColorSet* defaultColorSet = rServer->resourceByName(defaultPalette); - if (defaultColorSet) { - setColorSet(defaultColorSet); + QString defaultPaletteName = cfg.defaultPalette(); + KoColorSet* 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() -{ - KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); - rServer->removeObserver(this); - - if (m_currentColorSet) { - KisConfig cfg(true); - cfg.setDefaultPalette(m_currentColorSet->name()); - } - - delete m_wdgPaletteDock->paletteView->itemDelegate(); - delete m_wdgPaletteDock; -} +{ } void PaletteDockerDock::setViewManager(KisViewManager* kisview) { + m_view = kisview; m_resourceProvider = kisview->resourceProvider(); - connect(m_resourceProvider, SIGNAL(sigSavingWorkspace(KisWorkspaceResource*)), SLOT(saveToWorkspace(KisWorkspaceResource*))); - connect(m_resourceProvider, SIGNAL(sigLoadingWorkspace(KisWorkspaceResource*)), SLOT(loadFromWorkspace(KisWorkspaceResource*))); - connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)),m_wdgPaletteDock->paletteView, SLOT(trySelectClosestColor(KoColor))); + connect(m_resourceProvider, SIGNAL(sigSavingWorkspace(KisWorkspaceResource*)), + SLOT(saveToWorkspace(KisWorkspaceResource*))); + connect(m_resourceProvider, SIGNAL(sigLoadingWorkspace(KisWorkspaceResource*)), + SLOT(loadFromWorkspace(KisWorkspaceResource*))); + connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)), + m_ui->paletteView, SLOT(slotFGColorChanged(KoColor))); kisview->nodeManager()->disconnect(m_model); - - } -void PaletteDockerDock::setCanvas(KoCanvasBase *canvas) +void PaletteDockerDock::slotContextMenu(const QModelIndex &) { - setEnabled(canvas != 0); - if (canvas) { - KisCanvas2 *cv = qobject_cast(canvas); - m_model->setDisplayRenderer(cv->displayColorConverter()->displayRendererInterface()); + if (QApplication::mouseButtons() == Qt::RightButton) { + m_viewContextMenu.exec(QCursor::pos()); } - m_canvas = static_cast(canvas); } - -void PaletteDockerDock::unsetCanvas() +void PaletteDockerDock::slotAddPalette() { - setEnabled(false); - m_model->setDisplayRenderer(0); - m_canvas = 0; + m_paletteEditor->addPalette(); } -void PaletteDockerDock::unsetResourceServer() +void PaletteDockerDock::slotRemovePalette(KoColorSet *cs) { - KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); - rServer->removeObserver(this); + m_paletteEditor->removePalette(cs); } -void PaletteDockerDock::removingResource(KoColorSet *resource) +void PaletteDockerDock::slotImportPalette() { - if (resource == m_currentColorSet) { - setColorSet(0); - } + m_paletteEditor->importPalette(); } -void PaletteDockerDock::resourceChanged(KoColorSet *resource) +void PaletteDockerDock::slotExportPalette(KoColorSet *palette) { - setColorSet(resource); + 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::setColorSet(KoColorSet* colorSet) +void PaletteDockerDock::setCanvas(KoCanvasBase *canvas) { - m_model->setColorSet(colorSet); - m_wdgPaletteDock->paletteView->updateView(); - m_wdgPaletteDock->paletteView->updateRows(); - m_wdgPaletteDock->cmbNameList->clear(); - - - - if (colorSet && colorSet->nColors()>0) { - for (quint32 i = 0; i< colorSet->nColors(); i++) { - KoColorSetEntry entry = colorSet->getColorGlobal(i); - QPixmap colorSquare = QPixmap(32, 32); - if (entry.spotColor()) { - QImage img = QImage(32, 32, QImage::Format_ARGB32); - QPainter circlePainter; - img.fill(Qt::transparent); - circlePainter.begin(&img); - QBrush brush = QBrush(Qt::SolidPattern); - brush.setColor(entry.color().toQColor()); - circlePainter.setBrush(brush); - QPen pen = circlePainter.pen(); - pen.setColor(Qt::transparent); - pen.setWidth(0); - circlePainter.setPen(pen); - circlePainter.drawEllipse(0, 0, 32, 32); - circlePainter.end(); - colorSquare = QPixmap::fromImage(img); - } else { - colorSquare.fill(entry.color().toQColor()); - } - QString name = entry.name(); - if (!entry.id().isEmpty()){ - name = entry.id() + " - " + entry.name(); - } - m_wdgPaletteDock->cmbNameList->addSqueezedItem(QIcon(colorSquare), name); + setEnabled(canvas != Q_NULLPTR); + if (canvas) { + KisCanvas2 *cv = qobject_cast(canvas); + m_ui->paletteView->setDisplayRenderer(cv->displayColorConverter()->displayRendererInterface()); + } + + if (m_activeDocument) { + for (KoColorSet * &cs : m_activeDocument->paletteList()) { + KoColorSet *tmpAddr = cs; + cs = new KoColorSet(*cs); + m_rServer->removeResourceFromServer(tmpAddr); } } + if (m_view && m_view->document()) { + m_activeDocument = m_view->document(); + m_paletteEditor->setView(m_view); + + for (KoColorSet *cs : m_activeDocument->paletteList()) { + m_rServer->addResource(cs); + } + } + + if (!m_currentColorSet) { + slotSetColorSet(Q_NULLPTR); + } +} +void PaletteDockerDock::unsetCanvas() +{ + setEnabled(false); + m_ui->paletteView->setDisplayRenderer(Q_NULLPTR); + m_paletteEditor->setView(Q_NULLPTR); + for (KoResource *r : m_rServer->resources()) { + KoColorSet *c = static_cast(r); + if (!c->isGlobal()) { + m_rServer->removeResourceFromServer(c); + } + } + if (!m_currentColorSet) { + slotSetColorSet(Q_NULLPTR); + } +} - QCompleter *completer = new QCompleter(m_wdgPaletteDock->cmbNameList->model()); - completer->setCompletionMode(QCompleter::PopupCompletion); - completer->setCaseSensitivity(Qt::CaseInsensitive); - completer->setFilterMode(Qt::MatchContains); - m_wdgPaletteDock->cmbNameList->setCompleter(completer); - if (colorSet && colorSet->removable()) { - m_wdgPaletteDock->bnAdd->setEnabled(true); - m_wdgPaletteDock->bnRemove->setEnabled(true); +void PaletteDockerDock::slotSetColorSet(KoColorSet* 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_wdgPaletteDock->bnAdd->setEnabled(false); - m_wdgPaletteDock->bnRemove->setEnabled(false); + 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->bnColorSets->setText(colorSet->name()); + } else { + m_ui->bnColorSets->setText(""); + } } -void PaletteDockerDock::setColorFromNameList(int index) +void PaletteDockerDock::slotEditPalette() { - if (m_model && m_currentColorSet) { - entrySelected(m_currentColorSet->getColorGlobal(index)); - m_wdgPaletteDock->paletteView->blockSignals(true); - m_wdgPaletteDock->cmbNameList->blockSignals(true); - m_wdgPaletteDock->cmbNameList->setCurrentIndex(index); - m_wdgPaletteDock->paletteView->selectionModel()->clearSelection(); - m_wdgPaletteDock->paletteView->selectionModel()->setCurrentIndex(m_model->indexFromId(index), QItemSelectionModel::Select); - m_wdgPaletteDock->paletteView->blockSignals(false); - m_wdgPaletteDock->cmbNameList->blockSignals(false); - } + 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::addColorForeground() + +void PaletteDockerDock::slotAddColor() { if (m_resourceProvider) { - //setup dialog - m_wdgPaletteDock->paletteView->addEntryWithDialog(m_resourceProvider->fgColor()); + m_paletteEditor->addEntry(m_resourceProvider->fgColor()); } } -void PaletteDockerDock::removeColor() +void PaletteDockerDock::slotRemoveColor() { - QModelIndex index = m_wdgPaletteDock->paletteView->currentIndex(); + QModelIndex index = m_ui->paletteView->currentIndex(); if (!index.isValid()) { return; } - m_wdgPaletteDock->paletteView->removeEntryWithDialog(index); + m_paletteEditor->removeEntry(index); + m_ui->bnRemove->setEnabled(false); } -void PaletteDockerDock::entrySelected(KoColorSetEntry entry) +void PaletteDockerDock::setFGColorByPalette(const KisSwatch &entry) { - if (m_wdgPaletteDock->paletteView->currentIndex().isValid()) { - quint32 index = m_model->idFromIndex(m_wdgPaletteDock->paletteView->currentIndex()); - m_wdgPaletteDock->cmbNameList->setCurrentIndex(index); - } if (m_resourceProvider) { m_resourceProvider->setFGColor(entry.color()); } - if (m_currentColorSet->removable()) { - m_wdgPaletteDock->bnRemove->setEnabled(true); - } -} - -void PaletteDockerDock::entrySelectedBack(KoColorSetEntry entry) -{ - if (m_wdgPaletteDock->paletteView->currentIndex().isValid()) { - quint32 index = m_model->idFromIndex(m_wdgPaletteDock->paletteView->currentIndex()); - m_wdgPaletteDock->cmbNameList->setCurrentIndex(index); - } - if (m_resourceProvider) { - m_resourceProvider->setBGColor(entry.color()); - } - if (m_currentColorSet->removable()) { - m_wdgPaletteDock->bnRemove->setEnabled(true); - } } void PaletteDockerDock::saveToWorkspace(KisWorkspaceResource* workspace) { - if (m_currentColorSet) { + if (!m_currentColorSet.isNull()) { workspace->setProperty("palette", m_currentColorSet->name()); } } void PaletteDockerDock::loadFromWorkspace(KisWorkspaceResource* workspace) { if (workspace->hasProperty("palette")) { KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); KoColorSet* colorSet = rServer->resourceByName(workspace->getString("palette")); if (colorSet) { - setColorSet(colorSet); + slotSetColorSet(colorSet); } } } +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_resourceProvider->setFGColor(color); +} diff --git a/plugins/dockers/palettedocker/palettedocker_dock.h b/plugins/dockers/palettedocker/palettedocker_dock.h index 4736a700a8..c56fe112c4 100644 --- a/plugins/dockers/palettedocker/palettedocker_dock.h +++ b/plugins/dockers/palettedocker/palettedocker_dock.h @@ -1,86 +1,111 @@ /* * 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 KisWorkspaceResource; -class KisColorsetChooser; +class KisPaletteListWidget; class KisPaletteModel; -class Ui_WdgPaletteDock; +class KisPaletteEditor; +class Ui_WdgPaletteDock; -class PaletteDockerDock : public QDockWidget, public KisMainwindowObserver, public KoResourceServerObserver { +class PaletteDockerDock : public QDockWidget, public KisMainwindowObserver +{ Q_OBJECT public: PaletteDockerDock(); ~PaletteDockerDock() override; - QString observerName() override { return "PaletteDockerDock"; } - void setViewManager(KisViewManager* kisview) override; + +public: // QDockWidget void setCanvas(KoCanvasBase *canvas) override; void unsetCanvas() override; -public: // KoResourceServerObserver - - void unsetResourceServer() override; - void resourceAdded(KoColorSet *) override {} - void removingResource(KoColorSet *resource) override; - void resourceChanged(KoColorSet *resource) override; - void syncTaggedResourceView() override {} - void syncTagAddition(const QString&) override {} - void syncTagRemoval(const QString&) override {} +public: // KisMainWindowObserver + void setViewManager(KisViewManager* kisview) override; private Q_SLOTS: - void addColorForeground(); - void removeColor(); - void entrySelected(KoColorSetEntry entry); - void entrySelectedBack(KoColorSetEntry entry); - void setColorSet(KoColorSet* colorSet); + void slotContextMenu(const QModelIndex &); + + void slotAddPalette(); + void slotRemovePalette(KoColorSet *); + void slotImportPalette(); + void slotExportPalette(KoColorSet *); - void setColorFromNameList(int index); + 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(KoColorSet* colorSet); void saveToWorkspace(KisWorkspaceResource* workspace); void loadFromWorkspace(KisWorkspaceResource* workspace); -private: - Ui_WdgPaletteDock* m_wdgPaletteDock; +private: + void setEntryByForeground(const QModelIndex &index); + void setFGColorByPalette(const KisSwatch &entry); + +private /* member variables */: + QScopedPointer m_ui; KisPaletteModel *m_model; - QSharedPointer m_serverAdapter; - KoColorSet *m_currentColorSet; - KisColorsetChooser *m_paletteChooser; + KisPaletteListWidget *m_paletteChooser; + + QPointer m_view; KisCanvasResourceProvider *m_resourceProvider; - QPointer m_canvas; + + KoResourceServer * const m_rServer; + + QPointer m_activeDocument; + QPointer m_currentColorSet; + QScopedPointer m_paletteEditor; + + QScopedPointer m_actAdd; + QScopedPointer m_actRemove; + QScopedPointer m_actModify; + QScopedPointer m_actEditPalette; + QMenu m_viewContextMenu; }; #endif diff --git a/plugins/dockers/palettedocker/wdgpalettedock.ui b/plugins/dockers/palettedocker/wdgpalettedock.ui index 11deff9c33..f937f1fcd1 100644 --- a/plugins/dockers/palettedocker/wdgpalettedock.ui +++ b/plugins/dockers/palettedocker/wdgpalettedock.ui @@ -1,151 +1,161 @@ WdgPaletteDock 0 0 - 326 - 219 + 647 + 422 1 0 0 0 0 0 16 + + + + true + + + - + Add foreground color ... + + + .. + 22 22 false - + Delete color ... + + + .. + 22 22 false - - + + - - - - - - ... + + + .. - - - - - 0 - 0 - + + + + Qt::Horizontal - - true + + + 40 + 20 + - + - - - - Add color + + + + + + + + ... - - - 22 - 22 - - - - false + + KisPopupButton QPushButton
kis_popup_button.h
KisPaletteView QTableView
kis_palette_view.h
- SqueezedComboBox + KisPaletteComboBox QComboBox -
squeezedcombobox.h
+
KisPaletteComboBox.h
bnRemove
diff --git a/plugins/extensions/layersplit/dlg_layersplit.cpp b/plugins/extensions/layersplit/dlg_layersplit.cpp index a8b5889175..bae3035cd9 100644 --- a/plugins/extensions/layersplit/dlg_layersplit.cpp +++ b/plugins/extensions/layersplit/dlg_layersplit.cpp @@ -1,148 +1,148 @@ /* * 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 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 KisColorsetChooser(); + m_colorSetChooser = new KisPaletteListWidget(); m_page->paletteChooser->setPopupWidget(m_colorSetChooser); - connect(m_colorSetChooser, SIGNAL(paletteSelected(KoColorSet*)), this, SLOT(slotSetPalette(KoColorSet*))); + connect(m_colorSetChooser, SIGNAL(sigPaletteSelected(KoColorSet*)), this, SLOT(slotSetPalette(KoColorSet*))); KisConfig cfg(true); m_page->intFuzziness->setValue(cfg.readEntry("layersplit/fuzziness", 20)); m_page->chkCreateGroupLayer->setChecked(cfg.readEntry("layerspit/createmastergroup", true)); m_page->chkSeparateGroupLayers->setChecked(cfg.readEntry("layerspit/separategrouplayers", false)); m_page->chkAlphaLock->setChecked(cfg.readEntry("layerspit/alphalock", true)); m_page->chkHideOriginal->setChecked(cfg.readEntry("layerspit/hideoriginal", false)); m_page->chkSortLayers->setChecked(cfg.readEntry("layerspit/sortlayers", true)); m_page->chkDisregardOpacity->setChecked(cfg.readEntry("layerspit/disregardopacity", true)); QString paletteName = cfg.readEntry("layersplit/paletteName", i18n("Default")); KoResourceServer *pserver = KoResourceServerProvider::instance()->paletteServer(); KoColorSet *pal = pserver->resourceByName(paletteName); 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(applyClicked())); setMainWidget(m_page); } DlgLayerSplit::~DlgLayerSplit() { } void DlgLayerSplit::applyClicked() { KisConfig cfg(false); cfg.writeEntry("layersplit/fuzziness", m_page->intFuzziness->value()); cfg.writeEntry("layerspit/createmastergroup", m_page->chkCreateGroupLayer->isChecked()); cfg.writeEntry("layerspit/separategrouplayers", m_page->chkSeparateGroupLayers->isChecked()); cfg.writeEntry("layerspit/alphalock", m_page->chkAlphaLock->isChecked()); cfg.writeEntry("layerspit/hideoriginal", m_page->chkHideOriginal->isChecked()); cfg.writeEntry("layerspit/sortlayers", m_page->chkSortLayers->isChecked()); cfg.writeEntry("layerspit/disregardopacity", m_page->chkDisregardOpacity->isChecked()); 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(); } KoColorSet *DlgLayerSplit::palette() const { return m_palette; } void DlgLayerSplit::slotSetPalette(KoColorSet *pal) { if (pal) { m_palette = pal; m_page->paletteChooser->setText(pal->name()); QIcon icon(QPixmap::fromImage(pal->image())); m_page->paletteChooser->setIcon(icon); } } diff --git a/plugins/extensions/layersplit/dlg_layersplit.h b/plugins/extensions/layersplit/dlg_layersplit.h index b75aa5574e..4eed5029e1 100644 --- a/plugins/extensions/layersplit/dlg_layersplit.h +++ b/plugins/extensions/layersplit/dlg_layersplit.h @@ -1,63 +1,63 @@ /* * 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; KoColorSet* palette() const; private Q_SLOTS: void applyClicked(); void slotSetPalette(KoColorSet *pal); private: WdgLayerSplit *m_page {0}; - KisColorsetChooser *m_colorSetChooser {0}; + KisPaletteListWidget *m_colorSetChooser {0}; KoColorSet *m_palette {0}; }; #endif // DLG_LAYERSPLIT diff --git a/plugins/extensions/layersplit/layersplit.cpp b/plugins/extensions/layersplit/layersplit.cpp index f46d19d6f2..af8370cf8e 100644 --- a/plugins/extensions/layersplit/layersplit.cpp +++ b/plugins/extensions/layersplit/layersplit.cpp @@ -1,226 +1,226 @@ /* * 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 "layersplit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dlg_layersplit.h" #include "kis_node_manager.h" #include "kis_node_commands_adapter.h" #include "kis_undo_adapter.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(LayerSplitFactory, "kritalayersplit.json", registerPlugin();) LayerSplit::LayerSplit(QObject *parent, const QVariantList &) : KisActionPlugin(parent) { KisAction *action = createAction("layersplit"); connect(action, SIGNAL(triggered()), this, SLOT(slotLayerSplit())); } LayerSplit::~LayerSplit() { } struct Layer { KoColor color; KisPaintDeviceSP device; KisRandomAccessorSP accessor; int pixelsWritten; bool operator<(const Layer& other) const { return pixelsWritten < other.pixelsWritten; } }; void LayerSplit::slotLayerSplit() { DlgLayerSplit dlg; if (dlg.exec() == QDialog::Accepted) { dlg.hide(); QApplication::setOverrideCursor(Qt::WaitCursor); QPointer updater = viewManager()->createUnthreadedUpdater(i18n("Split into Layers")); KisImageSP image = viewManager()->image(); if (!image) return; image->lock(); KisNodeSP node = viewManager()->activeNode(); if (!node) return; KisPaintDeviceSP projection = node->projection(); if (!projection) return; QList colorMap; const KoColorSpace *cs = projection->colorSpace(); QRect rc = image->bounds(); int fuzziness = dlg.fuzziness(); updater->setProgress(0); KisRandomConstAccessorSP acc = projection->createRandomConstAccessorNG(rc.x(), rc.y()); for (int row = rc.y(); row < rc.height(); ++row) { for (int col = rc.x(); col < rc.width(); ++col) { acc->moveTo(col, row); KoColor c(cs); c.setColor(acc->rawDataConst(), cs); if (c.opacityU8() == OPACITY_TRANSPARENT_U8) { continue; } if (dlg.disregardOpacity()) { c.setOpacity(OPACITY_OPAQUE_U8); } bool found = false; Q_FOREACH (const Layer &l, colorMap) { if (fuzziness == 0) { found = (l.color == c); } else { quint8 match = cs->difference(l.color.data(), c.data()); found = (match <= fuzziness); } if (found) { KisRandomAccessorSP dstAcc = l.accessor; dstAcc->moveTo(col, row); memcpy(dstAcc->rawData(), acc->rawDataConst(), cs->pixelSize()); const_cast(&l)->pixelsWritten++; break; } } if (!found) { QString name = ""; if (dlg.palette()) { - name = dlg.palette()->closestColorName(c); + name = dlg.palette()->getClosestColorInfo(c).swatch.name(); } if (name.toLower() == "untitled" || name.toLower() == "none" || name.toLower() == "") { name = KoColor::toQString(c); } Layer l; l.color = c; l.device = new KisPaintDevice(cs, name); l.accessor = l.device->createRandomAccessorNG(col, row); l.accessor->moveTo(col, row); memcpy(l.accessor->rawData(), acc->rawDataConst(), cs->pixelSize()); l.pixelsWritten = 1; colorMap << l; } } if (updater->interrupted()) { return; } updater->setProgress((row - rc.y()) * 100 / rc.height() - rc.y()); } updater->setProgress(100); dbgKrita << "Created" << colorMap.size() << "layers"; // Q_FOREACH (const Layer &l, colorMap) { // dbgKrita << "\t" << l.device->objectName() << ":" << l.pixelsWritten; // } if (dlg.sortLayers()) { std::sort(colorMap.begin(), colorMap.end()); } KisUndoAdapter *undo = image->undoAdapter(); undo->beginMacro(kundo2_i18n("Split Layer")); KisNodeCommandsAdapter adapter(viewManager()); KisGroupLayerSP baseGroup = dynamic_cast(node->parent().data()); if (!baseGroup) { // Masks are never nested baseGroup = dynamic_cast(node->parent()->parent().data()); } if (dlg.hideOriginal()) { node->setVisible(false); } if (dlg.createBaseGroup()) { KisGroupLayerSP grp = new KisGroupLayer(image, i18n("Color"), OPACITY_OPAQUE_U8); adapter.addNode(grp, baseGroup, 1); baseGroup = grp; } Q_FOREACH (const Layer &l, colorMap) { KisGroupLayerSP grp = baseGroup; if (dlg.createSeparateGroups()) { grp = new KisGroupLayer(image, l.device->objectName(), OPACITY_OPAQUE_U8); adapter.addNode(grp, baseGroup, 1); } KisPaintLayerSP paintLayer = new KisPaintLayer(image, l.device->objectName(), OPACITY_OPAQUE_U8, l.device); adapter.addNode(paintLayer, grp, 0); paintLayer->setAlphaLocked(dlg.lockAlpha()); } undo->endMacro(); image->unlock(); image->setModified(); } QApplication::restoreOverrideCursor(); } #include "layersplit.moc" diff --git a/plugins/extensions/pykrita/sip/krita/Palette.sip b/plugins/extensions/pykrita/sip/krita/Palette.sip index 4780b3c65c..8c58ee94e9 100644 --- a/plugins/extensions/pykrita/sip/krita/Palette.sip +++ b/plugins/extensions/pykrita/sip/krita/Palette.sip @@ -1,48 +1,29 @@ -/* -struct KoColorSetEntry { - %TypeHeaderCode - #include "KoColorSet.h" - %End -public: - KoColorSetEntry(); - - QString name; - QString id; - bool spotColor; - - bool operator==(const KoColorSetEntry& rhs) const; - -}; -*/ -class KoColorSetEntry; +class KisSwatch; class Palette : QObject { %TypeHeaderCode #include "Palette.h" %End Palette(const Palette & __0); public: Palette(Resource *resource); int numberOfEntries() const; int columnCount(); void setColumnCount(int columns); QString comment(); void setComment(QString comment); QStringList groupNames(); bool addGroup(QString name); bool removeGroup(QString name, bool keepColors); int colorsCountTotal(); - int colorsCountGroup(QString name); - KoColorSetEntry colorSetEntryByIndex(int index); - KoColorSetEntry colorSetEntryFromGroup(int index, const QString &groupName); - ManagedColor *colorForEntry(KoColorSetEntry entry) /Factory/; - void addEntry(KoColorSetEntry entry, QString groupName); + KisSwatch colorSetEntryByIndex(int index); + KisSwatch colorSetEntryFromGroup(int index, const QString &groupName); + ManagedColor *colorForEntry(KisSwatch entry) /Factory/; + void addEntry(KisSwatch entry, QString groupName); void removeEntry(int index, const QString &groupName); - void insertEntry(int index, KoColorSetEntry entry, QString groupName); - bool editEntry(int index, KoColorSetEntry entry, QString groupName); bool changeGroupName(QString oldGroupName, QString newGroupName); bool moveGroup(const QString &groupName, const QString &groupNameInsertBefore); bool save(); private: }; diff --git a/plugins/extensions/pykrita/sip/krita/PaletteView.sip b/plugins/extensions/pykrita/sip/krita/PaletteView.sip index 21bc77be93..5714b366dd 100644 --- a/plugins/extensions/pykrita/sip/krita/PaletteView.sip +++ b/plugins/extensions/pykrita/sip/krita/PaletteView.sip @@ -1,20 +1,20 @@ class PaletteView : QWidget { %TypeHeaderCode #include "PaletteView.h" %End PaletteView(const PaletteView & __0); public: PaletteView(QWidget *parent = 0); ~PaletteView(); public Q_SLOTS: void setPalette(Palette *palette); bool addEntryWithDialog(ManagedColor *color); bool addGroupWithDialog(); bool removeSelectedEntryWithDialog(); void trySelectClosestColor(ManagedColor *color); Q_SIGNALS: - void entrySelectedForeGround(KoColorSetEntry entry); - void entrySelectedBackGround(KoColorSetEntry entry); + void entrySelectedForeGround(KisSwatch entry); + void entrySelectedBackGround(KisSwatch entry); private: }; diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp index 772d7930ec..504f7ceddd 100644 --- a/plugins/impex/kra/kra_converter.cpp +++ b/plugins/impex/kra/kra_converter.cpp @@ -1,355 +1,360 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kra_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char CURRENT_DTD_VERSION[] = "2.0"; KraConverter::KraConverter(KisDocument *doc) : m_doc(doc) , m_image(doc->savingImage()) { } KraConverter::~KraConverter() { delete m_store; delete m_kraSaver; delete m_kraLoader; } KisImageBuilder_Result KraConverter::buildImage(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Read, "", KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Not a valid Krita file")); return KisImageBuilder_RESULT_FAILURE; } bool success; { if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml) KoXmlDocument doc; bool ok = oldLoadAndParse(m_store, "root", doc); if (ok) ok = loadXML(doc, m_store); if (!ok) { return KisImageBuilder_RESULT_FAILURE; } } else { errUI << "ERROR: No maindoc.xml" << endl; m_doc->setErrorMessage(i18n("Invalid document: no file 'maindoc.xml'.")); return KisImageBuilder_RESULT_FAILURE; } if (m_store->hasFile("documentinfo.xml")) { KoXmlDocument doc; if (oldLoadAndParse(m_store, "documentinfo.xml", doc)) { m_doc->documentInfo()->load(doc); } } success = completeLoading(m_store); } return success ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE; } KisImageSP KraConverter::image() { return m_image; } vKisNodeSP KraConverter::activeNodes() { return m_activeNodes; } QList KraConverter::assistants() { return m_assistants; } KisImageBuilder_Result KraConverter::buildFile(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Write, m_doc->nativeFormatMimeType(), KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Could not create the file for saving")); return KisImageBuilder_RESULT_FAILURE; } bool result = false; m_kraSaver = new KisKraSaver(m_doc); result = saveRootDocuments(m_store); if (!result) { return KisImageBuilder_RESULT_FAILURE; } result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true); if (!result) { qWarning() << "saving key frames failed"; } result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving()); if (!result) { qWarning() << "saving binary data failed"; } + result = m_kraSaver->savePalettes(m_store, m_image, m_doc->url().toLocalFile()); + if (!result) { + qWarning() << "saving palettes data failed"; + } if (!m_store->finalize()) { return KisImageBuilder_RESULT_FAILURE; } if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); return KisImageBuilder_RESULT_FAILURE; } return KisImageBuilder_RESULT_OK; } bool KraConverter::saveRootDocuments(KoStore *store) { dbgFile << "Saving root"; if (store->open("root")) { KoStoreDevice dev(store); if (!saveToStream(&dev) || !store->close()) { dbgUI << "saveToStream failed"; return false; } } else { m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"))); return false; } bool success = false; if (store->open("documentinfo.xml")) { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = m_doc->documentInfo()->save(doc); KoStoreDevice dev(store); QByteArray s = doc.toByteArray(); // this is already Utf8! success = dev.write(s.data(), s.size()); store->close(); } if (store->open("preview.png")) { // ### TODO: missing error checking (The partition could be full!) savePreview(store); (void)store->close(); } dbgUI << "Saving done of url:" << m_doc->url().toLocalFile(); // Success return success; } bool KraConverter::saveToStream(QIODevice *dev) { QDomDocument doc = createDomDocument(); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open(QIODevice::WriteOnly); int nwritten = dev->write(s.data(), s.size()); if (nwritten != (int)s.size()) { warnUI << "wrote " << nwritten << "- expected" << s.size(); } return nwritten == (int)s.size(); } QDomDocument KraConverter::createDomDocument() { QDomDocument doc = m_doc->createDomDocument("DOC", CURRENT_DTD_VERSION); QDomElement root = doc.documentElement(); root.setAttribute("editor", "Krita"); root.setAttribute("syntaxVersion", "2"); root.setAttribute("kritaVersion", KritaVersionWrapper::versionString(false)); root.appendChild(m_kraSaver->saveXML(doc, m_image)); if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); } return doc; } bool KraConverter::savePreview(KoStore *store) { QPixmap pix = m_doc->generatePreview(QSize(256, 256)); QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); if (preview.size() == QSize(0,0)) { QSize newSize = m_doc->savingImage()->bounds().size(); newSize.scale(QSize(256, 256), Qt::KeepAspectRatio); preview = QImage(newSize, QImage::Format_ARGB32); preview.fill(QColor(0, 0, 0, 0)); } KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { return false; } bool ret = preview.save(&io, "PNG"); io.close(); return ret; } bool KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc) { //dbgUI <<"Trying to open" << filename; if (!store->open(filename)) { warnUI << "Entry " << filename << " not found!"; m_doc->setErrorMessage(i18n("Could not find %1", filename)); return false; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = xmldoc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); store->close(); if (!ok) { errUI << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; m_doc->setErrorMessage(i18n("Parsing error in %1 at line %2, column %3\nError message: %4" , filename , errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0))); return false; } dbgUI << "File" << filename << " loaded and parsed"; return true; } bool KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store) { Q_UNUSED(store); KoXmlElement root; KoXmlNode node; if (doc.doctype().name() != "DOC") { m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted")); return false; } root = doc.documentElement(); int syntaxVersion = root.attribute("syntaxVersion", "3").toInt(); if (syntaxVersion > 2) { m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion)); return false; } if (!root.hasChildNodes()) { m_doc->setErrorMessage(i18n("The file has no layers.")); return false; } m_kraLoader = new KisKraLoader(m_doc, syntaxVersion); // reset the old image before loading the next one m_doc->setCurrentImage(0, false); for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isElement()) { if (node.nodeName() == "IMAGE") { KoXmlElement elem = node.toElement(); if (!(m_image = m_kraLoader->loadXML(elem))) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } // HACK ALERT! m_doc->hackPreliminarySetImage(m_image); return true; } else { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("The file does not contain an image.")); } return false; } } } return false; } bool KraConverter::completeLoading(KoStore* store) { if (!m_image) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } m_image->blockUpdates(); m_kraLoader->loadBinaryData(store, m_image, m_doc->localFilePath(), true); + m_kraLoader->loadPalettes(store, m_doc); m_image->unblockUpdates(); if (!m_kraLoader->warningMessages().isEmpty()) { // warnings do not interrupt loading process, so we do not return here m_doc->setWarningMessage(m_kraLoader->warningMessages().join("\n")); } m_activeNodes = m_kraLoader->selectedNodes(); m_assistants = m_kraLoader->assistants(); return true; } void KraConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp index fb4d6ffcaf..8dd89a7954 100644 --- a/plugins/impex/libkra/kis_kra_loader.cpp +++ b/plugins/impex/libkra/kis_kra_loader.cpp @@ -1,1210 +1,1244 @@ /* 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 "lazybrush/kis_colorize_mask.h" #include #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" /* 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; QString name; qint32 width; qint32 height; QString profileProductName; double xres; double yres; QString colorspacename; const KoColorSpace * cs; if ((attr = element.attribute(MIME)) == NATIVE_MIMETYPE) { if ((m_d->imageName = element.attribute(NAME)).isNull()) { m_d->errorMessages << i18n("Image does not have a name."); return KisImageSP(0); } if ((attr = element.attribute(WIDTH)).isNull()) { m_d->errorMessages << i18n("Image does not specify a width."); return KisImageSP(0); } width = KisDomUtils::toInt(attr); if ((attr = element.attribute(HEIGHT)).isNull()) { m_d->errorMessages << i18n("Image does not specify a height."); return KisImageSP(0); } height = KisDomUtils::toInt(attr); m_d->imageComment = element.attribute(DESCRIPTION); xres = 100.0 / 72.0; if (!(attr = element.attribute(X_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { xres = value / 72.0; } } yres = 100.0 / 72.0; if (!(attr = element.attribute(Y_RESOLUTION)).isNull()) { qreal value = KisDomUtils::toDouble(attr); if (value > 1.0) { yres = value / 72.0; } } if ((colorspacename = element.attribute(COLORSPACE_NAME)).isNull()) { // An old file: take a reasonable default. // Krita didn't support anything else in those // days anyway. colorspacename = "RGBA"; } profileProductName = element.attribute(PROFILE); // A hack for an old colorspacename convertColorSpaceNames(colorspacename, profileProductName); QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id(); QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id(); if (profileProductName.isNull()) { // no mention of profile so get default profile"; cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, profileProductName); } if (cs == 0) { // try once more without the profile cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, ""); if (cs == 0) { m_d->errorMessages << i18n("Image specifies an unsupported color model: %1.", colorspacename); return KisImageSP(0); } } 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, name); } else { image = new KisImage(0, width, height, cs, name); } image->setResolution(xres, yres); loadNodes(element, image, const_cast(image->rootLayer().data())); KoXmlNode child; for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) { KoXmlElement e = child.toElement(); if(e.tagName() == CANVASPROJECTIONCOLOR) { if (e.hasAttribute(COLORBYTEDATA)) { QByteArray colorData = QByteArray::fromBase64(e.attribute(COLORBYTEDATA).toLatin1()); KoColor color((const quint8*)colorData.data(), image->colorSpace()); image->setDefaultProjectionColor(color); } } if(e.tagName() == 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() == "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); } if (!res) { const QString defaultProfileId = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(image->colorSpace()->id()); profile = KoColorSpaceRegistry::instance()->profileByName(defaultProfileId); Q_ASSERT(profile && profile->valid()); image->assignImageProfile(profile); } } } } //load the embed proofing profile, it only needs to be loaded into Krita, not assigned. location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->hasFile(location)) { if (store->open(location)) { QByteArray proofingData; proofingData.resize(store->size()); bool proofingProfileRes = (store->read(proofingData.data(), store->size())>-1); store->close(); 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)) { KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource("Embedded Styles.asl"); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_d->imageName)); KIS_ASSERT_RECOVER_NOOP(!collection->valid()); store->open(location); { KoStoreDevice device(store); device.open(QIODevice::ReadOnly); /** * ASL loading code cannot work with non-sequential IO devices, * so convert the device beforehand! */ QByteArray buf = device.readAll(); QBuffer raDevice(&buf); raDevice.open(QIODevice::ReadOnly); collection->loadFromDevice(&raDevice); } store->close(); if (collection->valid()) { KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); collection->assignAllLayerStyles(image->root()); } else { warnKrita << "WARNING: Couldn't load layer styles library from .kra!"; delete collection; } } if (m_d->document && m_d->document->documentInfo()->aboutInfo("title").isNull()) m_d->document->documentInfo()->setAboutInfo("title", m_d->imageName); if (m_d->document && m_d->document->documentInfo()->aboutInfo("comment").isNull()) m_d->document->documentInfo()->setAboutInfo("comment", m_d->imageComment); loadAssistants(store, uri, external); } +void KisKraLoader::loadPalettes(KoStore *store, KisDocument *doc) +{ + QList list; + Q_FOREACH (const QString &filename, m_d->paletteFilenames) { + KoColorSet *newPalette = new KoColorSet(filename); + store->open(m_d->imageName + PALETTE_PATH + filename); + QByteArray data = store->read(store->size()); + newPalette->fromByteArray(data); + 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; } 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()); 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->defaultConfiguration(); 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->defaultConfiguration(); // We'll load the configuration and the selection later. layer = new KisGeneratorLayer(image, name, kgc, 0); Q_CHECK_PTR(layer); layer->setOpacity(opacity); return layer; } KisNodeSP KisKraLoader::loadCloneLayer(const KoXmlElement& element, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity) { Q_UNUSED(cs); KisCloneLayerSP layer = new KisCloneLayer(0, image, name, opacity); 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->defaultConfiguration(); // We'll load the configuration and the selection later. mask = new KisFilterMask(); mask->setFilter(kfc); Q_CHECK_PTR(mask); return mask; } KisNodeSP KisKraLoader::loadTransformMask(const KoXmlElement& element) { 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::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_loader.h b/plugins/impex/libkra/kis_kra_loader.h index 8bdeb429a9..237ce4ccd5 100644 --- a/plugins/impex/libkra/kis_kra_loader.h +++ b/plugins/impex/libkra/kis_kra_loader.h @@ -1,119 +1,121 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2007 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_KRA_LOADER_H #define KIS_KRA_LOADER_H class QString; class QStringList; #include "KoXmlReaderForward.h" class KoStore; class KisDocument; class KoColorSpace; class KisPaintingAssistant; #include #include "kritalibkra_export.h" /** * Load old-style 1.x .kra files. Updated for 2.0, let's try to stay * compatible. But 2.0 won't be able to save 1.x .kra files unless we * implement an export filter. */ class KRITALIBKRA_EXPORT KisKraLoader { public: KisKraLoader(KisDocument * document, int syntaxVersion); ~KisKraLoader(); /** * Loading is done in two steps: first all xml is loaded, then, in finishLoading, * the actual layer data is loaded. */ KisImageSP loadXML(const KoXmlElement& elem); void loadBinaryData(KoStore* store, KisImageSP image, const QString & uri, bool external); + void loadPalettes(KoStore *store, KisDocument *doc); + vKisNodeSP selectedNodes() const; // it's neater to follow the same design as with selectedNodes, so let's have a getter here QList assistants() const; /// if empty, loading didn't fail... QStringList errorMessages() const; /// if not empty, loading didn't fail, but there are problems QStringList warningMessages() const; private: // this needs to be private, for neatness sake void loadAssistants(KoStore* store, const QString & uri, bool external); void loadAnimationMetadata(const KoXmlElement& element, KisImageSP image); KisNodeSP loadNodes(const KoXmlElement& element, KisImageSP image, KisNodeSP parent); KisNodeSP loadNode(const KoXmlElement& elem, KisImageSP image); KisNodeSP loadPaintLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity); KisNodeSP loadGroupLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity); KisNodeSP loadAdjustmentLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity); KisNodeSP loadShapeLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity); KisNodeSP loadGeneratorLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity); KisNodeSP loadCloneLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, const KoColorSpace* cs, quint32 opacity); KisNodeSP loadFilterMask(const KoXmlElement& elem); KisNodeSP loadTransformMask(const KoXmlElement& elem); KisNodeSP loadTransparencyMask(const KoXmlElement& elem); KisNodeSP loadSelectionMask(KisImageSP image, const KoXmlElement& elem); KisNodeSP loadColorizeMask(KisImageSP image, const KoXmlElement& elem, const KoColorSpace *colorSpace); KisNodeSP loadFileLayer(const KoXmlElement& elem, KisImageSP image, const QString& name, quint32 opacity); KisNodeSP loadReferenceImagesLayer(const KoXmlElement& elem, KisImageSP image); void loadNodeKeyframes(KoStore *store, const QString &location, KisNodeSP node); void loadCompositions(const KoXmlElement& elem, KisImageSP image); void loadAssistantsList(const KoXmlElement& elem); void loadGrid(const KoXmlElement& elem); void loadGuides(const KoXmlElement& elem); void loadAudio(const KoXmlElement& elem, KisImageSP image); private: struct Private; Private * const m_d; }; #endif diff --git a/plugins/impex/libkra/kis_kra_saver.cpp b/plugins/impex/libkra/kis_kra_saver.cpp index 8850aef64d..2fb2e0d8e0 100644 --- a/plugins/impex/libkra/kis_kra_saver.cpp +++ b/plugins/impex/libkra/kis_kra_saver.cpp @@ -1,464 +1,510 @@ /* 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 #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 using namespace KRA; struct KisKraSaver::Private { public: KisDocument* doc; QMap nodeFileNames; QMap keyframeFilenames; QString imageName; QStringList errorMessages; }; KisKraSaver::KisKraSaver(KisDocument* document) : m_d(new Private) { m_d->doc = document; 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); - saveAudio(doc,imageElement); + saveGrid(doc, imageElement); + saveGuides(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); + + bool res = false; + if (m_d->doc->paletteList().size() == 0) { + return true; + } + for (const KoColorSet *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; + } + } + return res; +} + +void KisKraSaver::savePalettesToXML(QDomDocument &doc, QDomElement &element) +{ + QDomElement ePalette = doc.createElement(PALETTES); + for (const KoColorSet *palette : m_d->doc->paletteList()) { + if (!palette->isGlobal()) { + 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(); } } } { 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::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->doc->localFilePath()).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/impex/libkra/kis_kra_saver.h b/plugins/impex/libkra/kis_kra_saver.h index e7e06222c3..3147cc92f2 100644 --- a/plugins/impex/libkra/kis_kra_saver.h +++ b/plugins/impex/libkra/kis_kra_saver.h @@ -1,65 +1,68 @@ /* 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. */ #ifndef KIS_KRA_SAVER #define KIS_KRA_SAVER #include class KisDocument; class QDomElement; class QDomDocument; class KoStore; class QString; class QStringList; #include "kritalibkra_export.h" class KRITALIBKRA_EXPORT KisKraSaver { public: KisKraSaver(KisDocument* document); ~KisKraSaver(); QDomElement saveXML(QDomDocument& doc, KisImageSP image); bool saveKeyframes(KoStore *store, const QString &uri, bool external); bool saveBinaryData(KoStore* store, KisImageSP image, const QString & uri, bool external, bool includeMerge); + bool savePalettes(KoStore *store, KisImageSP image, const QString &uri); + /// @return a list with everything that went wrong while saving QStringList errorMessages() const; private: void saveBackgroundColor(QDomDocument& doc, QDomElement& element, KisImageSP image); void saveAssistantsGlobalColor(QDomDocument& doc, QDomElement& element); void saveWarningColor(QDomDocument& doc, QDomElement& element, KisImageSP image); void saveCompositions(QDomDocument& doc, QDomElement& element, KisImageSP image); bool saveAssistants(KoStore *store,QString uri, bool external); bool saveAssistantsList(QDomDocument& doc, QDomElement& element); bool saveGrid(QDomDocument& doc, QDomElement& element); bool saveGuides(QDomDocument& doc, QDomElement& element); bool saveAudio(QDomDocument& doc, QDomElement& element); bool saveNodeKeyframes(KoStore *store, QString location, const KisNode *node); + void savePalettesToXML(QDomDocument& doc, QDomElement &element); struct Private; Private * const m_d; }; #endif diff --git a/plugins/impex/libkra/kis_kra_tags.h b/plugins/impex/libkra/kis_kra_tags.h index fb2e8344f8..132b81ad9f 100644 --- a/plugins/impex/libkra/kis_kra_tags.h +++ b/plugins/impex/libkra/kis_kra_tags.h @@ -1,141 +1,143 @@ /* 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. */ #ifndef KIS_KRA_TAGS #define KIS_KRA_TAGS #include /** * Tag definitions for our xml file format */ namespace KRA { // mimetype const QString NATIVE_MIMETYPE = "application/x-kra"; // xml tags const QString SEPARATOR = "/"; const QString SHAPE_LAYER_PATH = "/shapelayers/"; const QString EXIF_PATH = "/annotations/exif"; const QString ICC_PATH = "/annotations/icc"; const QString ICC_PROOFING_PATH = "/annotations/proofing/icc"; const QString LAYER_STYLES_PATH = "/annotations/layerstyles.asl"; const QString ASSISTANTS_PATH = "/assistants/"; const QString LAYER_PATH = "/layers/"; +const QString PALETTE_PATH = "/palettes/"; const QString ADJUSTMENT_LAYER = "adjustmentlayer"; const QString CHANNEL_FLAGS = "channelflags"; const QString CLONE_FROM = "clonefrom"; const QString CLONE_FROM_UUID = "clonefromuuid"; const QString CLONE_LAYER = "clonelayer"; const QString CLONE_TYPE = "clonetype"; const QString COLORSPACE_NAME = "colorspacename"; const QString COMPOSITE_OP = "compositeop"; const QString DESCRIPTION = "description"; const QString ONION_SKIN_ENABLED = "onionskin"; const QString VISIBLE_IN_TIMELINE = "intimeline"; const QString DOT_FILTERCONFIG = ".filterconfig"; const QString DOT_TRANSFORMCONFIG = ".transformconfig"; const QString DOT_ICC = ".icc"; const QString DOT_PIXEL_SELECTION = ".pixelselection"; const QString DOT_SHAPE_SELECTION = ".shapeselection"; const QString DOT_SHAPE_LAYER = ".shapelayer"; const QString DOT_COLORIZE_MASK = ".colorizemask"; const QString DOT_METADATA = ".metadata"; const QString FILE_NAME = "filename"; const QString FILTER_MASK = "filtermask"; const QString FILTER_NAME = "filtername"; const QString FILTER_STATEGY = "filter_strategy"; const QString FILTER_VERSION = "filterversion"; const QString GENERATOR_LAYER = "generatorlayer"; const QString GENERATOR_NAME = "generatorname"; const QString GENERATOR_VERSION = "generatorversion"; const QString GROUP_LAYER = "grouplayer"; const QString HEIGHT = "height"; const QString ICC = "icc"; const QString LAYER = "layer"; const QString LAYERS = "layers"; const QString NODE_TYPE = "nodetype"; const QString LOCKED = "locked"; const QString MASK = "mask"; const QString MASKS = "masks"; const QString MIME = "mime"; const QString NAME = "name"; const QString OPACITY = "opacity"; const QString COLLAPSED = "collapsed"; const QString COLOR_LABEL = "colorlabel"; const QString PAINT_LAYER = "paintlayer"; const QString PROFILE = "profile"; const QString ROTATION = "rotation"; const QString SELECTION_MASK = "selectionmask"; const QString SHAPE_LAYER = "shapelayer"; const QString REFERENCE_IMAGES_LAYER = "referenceimages"; const QString FILE_LAYER = "filelayer"; const QString TRANSPARENCY_MASK = "transparencymask"; const QString COLORIZE_MASK = "colorizemask"; const QString COLORIZE_SHOW_COLORING = "show-coloring"; const QString COLORIZE_EDIT_KEYSTROKES = "edit-keystrokes"; const QString COLORIZE_KEYSTROKE = "keystroke"; const QString COLORIZE_KEYSTROKE_COLOR = "color"; const QString COLORIZE_KEYSTROKE_IS_TRANSPARENT = "is-transparent"; const QString COLORIZE_COLORING_DEVICE = "colorize-coloring"; const QString COLORIZE_KEYSTROKES_SECTION = "keystrokes"; const QString COLORIZE_USE_EDGE_DETECTION = "use-edge-detection"; const QString COLORIZE_EDGE_DETECTION_SIZE = "edge-detection-size"; const QString COLORIZE_FUZZY_RADIUS = "fuzzy-radius"; const QString COLORIZE_CLEANUP = "cleanup"; const QString COLORIZE_LIMIT_TO_DEVICE = "limit-to-device"; const QString TRANSFORM_MASK = "transformmask"; const QString UUID = "uuid"; const QString VISIBLE = "visible"; const QString WIDTH = "width"; const QString X = "x"; const QString X_RESOLUTION = "x-res"; const QString X_SCALE = "x_scale"; const QString X_SHEAR = "x_shear"; const QString X_TRANSLATION = "x_translation"; const QString Y = "y"; const QString Y_RESOLUTION = "y-res"; const QString Y_SCALE = "y_scale"; const QString Y_SHEAR = "y_shear"; const QString Y_TRANSLATION = "y_translation"; const QString ACTIVE = "active"; const QString LAYER_STYLE_UUID = "layerstyle"; const QString PASS_THROUGH_MODE = "passthrough"; const QString KEYFRAME_FILE = "keyframes"; const QString PROOFINGPROFILENAME = "proofing-profile-name"; const QString PROOFINGMODEL = "proofing-model"; const QString PROOFINGDEPTH = "proofing-depth"; const QString PROOFINGINTENT = "proofing-intent"; const QString PROOFINGWARNINGCOLOR ="ProofingWarningColor"; const QString PROOFINGADAPTATIONSTATE = "proofing-adaptation-state"; const QString ICCPROOFINGPROFILE ="icc-proofing-profile"; const QString CANVASPROJECTIONCOLOR = "ProjectionBackgroundColor"; const QString COLORBYTEDATA = "ColorData"; const QString SIMPLECOLORDATA = "SimpleColorData"; // easier 8-bit color data that works well with XML const QString GLOBALASSISTANTSCOLOR = "GlobalAssistantsColor"; +const QString PALETTES = "Palettes"; } #endif diff --git a/plugins/python/palette_docker/palette_docker.py b/plugins/python/palette_docker/palette_docker.py index 5344b302ef..8b6c6253d7 100644 --- a/plugins/python/palette_docker/palette_docker.py +++ b/plugins/python/palette_docker/palette_docker.py @@ -1,262 +1,262 @@ ''' Description: A Python based docker that allows you to edit KPL color palettes. By Wolthera(originally) This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode @package palette_docker ''' # Importing the relevant dependencies: import sys from PyQt5.QtGui import QPixmap, QIcon, QImage, QPainter, QBrush, QPalette from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QComboBox, QAction, QTabWidget, QLineEdit, QSpinBox, QDialogButtonBox, QToolButton, QDialog, QPlainTextEdit, QCompleter, QMenu from PyQt5.Qt import Qt, pyqtSignal, pyqtSlot import math from krita import * # import the exporters from . import palette_exporter_gimppalette, palette_exporter_inkscapeSVG, palette_sortColors class Palette_Docker(DockWidget): # Init the docker def __init__(self): super(Palette_Docker, self).__init__() # make base-widget and layout widget = QWidget() layout = QVBoxLayout() buttonLayout = QHBoxLayout() widget.setLayout(layout) self.setWindowTitle(i18n("Python Palette Docker")) # Make a combobox and add palettes self.cmb_palettes = QComboBox() allPalettes = Application.resources("palette") for palette_name in allPalettes: self.cmb_palettes.addItem(palette_name) self.cmb_palettes.model().sort(0) if len(allPalettes.keys()) > 0: self.currentPalette = Palette(allPalettes[list(allPalettes.keys())[0]]) else: self.currentPalette = None self.cmb_palettes.currentTextChanged.connect(self.slot_paletteChanged) layout.addWidget(self.cmb_palettes) # add combobox to the layout self.paletteView = PaletteView() self.paletteView.setPalette(self.currentPalette) layout.addWidget(self.paletteView) self.paletteView.entrySelectedForeGround.connect(self.slot_swatchSelected) self.colorComboBox = QComboBox() self.colorList = list() buttonLayout.addWidget(self.colorComboBox) self.bnSetColor = QToolButton() self.bnSetColor.setText(i18n("Set")) self.bnSetColor.clicked.connect(self.slot_get_color_from_combobox) buttonLayout.addWidget(self.bnSetColor) self.addEntry = QAction(self) self.addEntry.setIconText(i18n("+")) self.addEntry.triggered.connect(self.slot_add_entry) self.addGroup = QAction(self) self.addGroup.triggered.connect(self.slot_add_group) self.addGroup.setText(i18n("Add Group")) self.addGroup.setIconText(str("\U0001F4C2")) self.removeEntry = QAction(self) self.removeEntry.setText(i18n("Remove Entry")) self.removeEntry.setIconText("-") self.removeEntry.triggered.connect(self.slot_remove_entry) addEntryButton = QToolButton() addEntryButton.setDefaultAction(self.addEntry) buttonLayout.addWidget(addEntryButton) addGroupButton = QToolButton() addGroupButton.setDefaultAction(self.addGroup) buttonLayout.addWidget(addGroupButton) removeEntryButton = QToolButton() removeEntryButton.setDefaultAction(self.removeEntry) buttonLayout.addWidget(removeEntryButton) # QActions self.extra = QToolButton() self.editPaletteData = QAction(self) self.editPaletteData.setText(i18n("Edit Palette Settings")) self.editPaletteData.triggered.connect(self.slot_edit_palette_data) self.extra.setDefaultAction(self.editPaletteData) buttonLayout.addWidget(self.extra) self.actionMenu = QMenu() self.exportToGimp = QAction(self) self.exportToGimp.setText(i18n("Export as GIMP Palette File")) self.exportToGimp.triggered.connect(self.slot_export_to_gimp_palette) self.exportToInkscape = QAction(self) self.exportToInkscape.setText(i18n("Export as Inkscape SVG with Swatches")) self.exportToInkscape.triggered.connect(self.slot_export_to_inkscape_svg) self.sortColors = QAction(self) self.sortColors.setText(i18n("Sort Colors")) self.sortColors.triggered.connect(self.slot_sort_colors) self.actionMenu.addAction(self.editPaletteData) self.actionMenu.addAction(self.exportToGimp) self.actionMenu.addAction(self.exportToInkscape) # self.actionMenu.addAction(self.sortColors) self.extra.setMenu(self.actionMenu) layout.addLayout(buttonLayout) self.slot_fill_combobox() self.setWidget(widget) # add widget to the docker def slot_paletteChanged(self, name): allPalettes = Application.resources("palette") if len(allPalettes) > 0 and name in allPalettes: self.currentPalette = Palette(Application.resources("palette")[name]) self.paletteView.setPalette(self.currentPalette) self.slot_fill_combobox() @pyqtSlot('PaletteEntry') def slot_swatchSelected(self, entry): if (self.canvas()) is not None: if (self.canvas().view()) is not None: name = entry.name() if len(entry.id) > 0: name = entry.id() + " - " + entry.name() if len(name) > 0: if name in self.colorList: self.colorComboBox.setCurrentIndex(self.colorList.index(name)) color = self.currentPalette.colorForEntry(entry) self.canvas().view().setForeGroundColor(color) ''' A function for making a combobox with the available colors. We use QCompleter on the colorComboBox so that people can type in the name of a color to select it. This is useful for people with carefully made palettes where the colors are named properly, which makes it easier for them to find colors. ''' def slot_fill_combobox(self): if self.currentPalette is None: pass palette = self.currentPalette self.colorComboBox.clear() self.colorList = list() - for i in range(palette.colorsCountTotal()): - entry = palette.colorSetEntryByIndex(i) + for info in palette.infoList(): + entry = info.swatch color = palette.colorForEntry(entry).colorForCanvas(self.canvas()) colorSquare = QPixmap(12, 12) if entry.spotColor() is True: img = colorSquare.toImage() circlePainter = QPainter() img.fill(self.colorComboBox.palette().color(QPalette.Base)) circlePainter.begin(img) brush = QBrush(Qt.SolidPattern) brush.setColor(color) circlePainter.setBrush(brush) circlePainter.pen().setWidth(0) circlePainter.drawEllipse(0, 0, 11, 11) circlePainter.end() colorSquare = QPixmap.fromImage(img) else: colorSquare.fill(color) name = entry.name() if len(entry.id()) > 0: name = entry.id() + " - " + entry.name() self.colorList.append(name) self.colorComboBox.addItem(QIcon(colorSquare), name) self.colorComboBox.setEditable(True) self.colorComboBox.setInsertPolicy(QComboBox.NoInsert) self.colorComboBox.completer().setCompletionMode(QCompleter.PopupCompletion) self.colorComboBox.completer().setCaseSensitivity(False) self.colorComboBox.completer().setFilterMode(Qt.MatchContains) def slot_get_color_from_combobox(self): if self.currentPalette is not None: entry = self.currentPalette.colorSetEntryByIndex(self.colorComboBox.currentIndex()) self.slot_swatchSelected(entry) def slot_add_entry(self): if (self.canvas()) is not None: if (self.canvas().view()) is not None: color = self.canvas().view().foregroundColor() success = self.paletteView.addEntryWithDialog(color) if success is True: self.slot_fill_combobox() def slot_add_group(self): success = self.paletteView.addGroupWithDialog() if success is True: self.slot_fill_combobox() def slot_remove_entry(self): success = self.paletteView.removeSelectedEntryWithDialog() if success is True: self.slot_fill_combobox() ''' A function for giving a gui to edit palette metadata... I also want this to be the way to edit the settings of the palette docker. ''' def slot_edit_palette_data(self): dialog = QDialog(self) tabWidget = QTabWidget() dialog.setWindowTitle(i18n("Edit Palette Data")) dialog.setLayout(QVBoxLayout()) dialog.layout().addWidget(tabWidget) paletteWidget = QWidget() paletteWidget.setLayout(QVBoxLayout()) tabWidget.addTab(paletteWidget, i18n("Palette Data")) paletteName = QLineEdit() paletteName.setText(self.cmb_palettes.currentText()) paletteWidget.layout().addWidget(paletteName) paletteColumns = QSpinBox() paletteColumns.setValue(self.currentPalette.columnCount()) paletteWidget.layout().addWidget(paletteColumns) paletteComment = QPlainTextEdit() paletteComment.appendPlainText(self.currentPalette.comment()) paletteWidget.layout().addWidget(paletteComment) buttons = QDialogButtonBox(QDialogButtonBox.Ok) dialog.layout().addWidget(buttons) buttons.accepted.connect(dialog.accept) # buttons.rejected.connect(dialog.reject()) if dialog.exec_() == QDialog.Accepted: Resource = Application.resources("palette")[self.cmb_palettes.currentText()] Resource.setName(paletteName.text()) self.currentPalette = Palette(Resource) self.currentPalette.setColumnCount(paletteColumns.value()) self.paletteView.setPalette(self.currentPalette) self.slot_fill_combobox() self.currentPalette.setComment(paletteComment.toPlainText()) self.currentPalette.save() def slot_export_to_gimp_palette(self): palette_exporter_gimppalette.gimpPaletteExporter(self.cmb_palettes.currentText()) def slot_export_to_inkscape_svg(self): palette_exporter_inkscapeSVG.inkscapeSVGExporter(self.cmb_palettes.currentText()) def slot_sort_colors(self): colorSorter = palette_sortColors.sortColors(self.cmb_palettes.currentText()) self.paletteView.setPalette(colorSorter.palette()) def canvasChanged(self, canvas): self.cmb_palettes.clear() allPalettes = Application.resources("palette") for palette_name in allPalettes: self.cmb_palettes.addItem(palette_name) self.cmb_palettes.model().sort(0) if self.currentPalette == None and len(allPalettes.keys()) > 0: self.currentPalette = Palette(allPalettes[list(allPalettes.keys())[0]]) # Add docker to the application :) Application.addDockWidgetFactory(DockWidgetFactory("palette_docker", DockWidgetFactoryBase.DockRight, Palette_Docker)) diff --git a/plugins/tools/basictools/kis_tool_colorpicker.cc b/plugins/tools/basictools/kis_tool_colorpicker.cc index 5fd0d69c2a..20e1b80ca5 100644 --- a/plugins/tools/basictools/kis_tool_colorpicker.cc +++ b/plugins/tools/basictools/kis_tool_colorpicker.cc @@ -1,358 +1,358 @@ /* * 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); 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(); } void KisToolColorPicker::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (m_config->addPalette) { - KoColorSetEntry ent; + KisSwatch ent; ent.setColor(m_pickedColor); // We don't ask for a name, too intrusive here KoColorSet *palette = m_palettes.at(m_optionsWidget->cmbPalette->currentIndex()); palette->add(ent); 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); } } } 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("%"); 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 (KoColorSet *palette, palettes) { if (palette) { m_optionsWidget->cmbPalette->addSqueezedItem(palette->name()); m_palettes.append(palette); } } 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->addPalette); 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->addPalette = 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(KoResource *resource) { KoColorSet *palette = dynamic_cast(resource); 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 1d29b56e8b..0d81311997 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,377 +1,379 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_lazy_brush_options_widget.h" #include "ui_kis_tool_lazy_brush_options_widget.h" #include #include "KisPaletteModel.h" #include "kis_config.h" #include #include "kis_canvas_resource_provider.h" #include "kis_signal_auto_connection.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_image.h" #include "kis_signals_blocker.h" #include "kis_signal_compressor.h" #include "kis_layer_properties_icons.h" struct KisToolLazyBrushOptionsWidget::Private { Private() - : transparentColorIndex(-1), - baseNodeChangedCompressor(500, KisSignalCompressor::FIRST_ACTIVE) + : baseNodeChangedCompressor(500, KisSignalCompressor::FIRST_ACTIVE) { } Ui_KisToolLazyBrushOptionsWidget *ui; KisPaletteModel *colorModel; KisCanvasResourceProvider *provider; KisSignalAutoConnectionsStore providerSignals; KisSignalAutoConnectionsStore maskSignals; KisColorizeMaskSP activeMask; KoColorSet colorSet; - int transparentColorIndex = -1; + 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(indexEntrySelected(QModelIndex)), this, SLOT(entrySelected(QModelIndex))); + 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; - const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); + m_d->colorSet.setIsGlobal(false); + m_d->colorSet.setIsEditable(true); + m_d->colorModel->setPalette(&m_d->colorSet); - m_d->colorSet.add(KoColorSetEntry(KoColor(Qt::red, cs), "color1")); - m_d->colorSet.add(KoColorSetEntry(KoColor(Qt::green, cs), "color2")); - m_d->colorSet.add(KoColorSetEntry(KoColor(Qt::blue, cs), "color3")); + const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); - m_d->colorModel->setColorSet(&m_d->colorSet); + 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; - const int i = m_d->colorModel->idFromIndex(index); + KisSwatch entry = m_d->colorModel->getEntry(index); + m_d->provider->setFGColor(entry.color()); - if (i >= 0 && i < (int)m_d->colorSet.nColors()) { - KoColorSetEntry entry = m_d->colorModel->colorSetEntryFromIndex(index); - m_d->provider->setFGColor(entry.color()); - } + int idxInList = m_d->activeMask->keyStrokesColors().colors.indexOf(entry.color()); - const bool transparentChecked = i >= 0 && i == m_d->transparentColorIndex; - KisSignalsBlocker b(m_d->ui->btnTransparent); - m_d->ui->btnTransparent->setChecked(transparentChecked); + 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) { - int selectedIndex = -1; + bool found = false; - for (quint32 i = 0; i < m_d->colorSet.nColors(); i++) { - KoColorSetEntry entry = m_d->colorSet.getColorGlobal(i); - if (entry.color() == color) { - selectedIndex = (int)i; - break; - } + QModelIndex candidateIdx = m_d->colorModel->indexForClosest(color); + if (m_d->colorModel->getEntry(candidateIdx).color() == color) { + found = true; } - m_d->ui->btnRemove->setEnabled(selectedIndex >= 0); - m_d->ui->btnTransparent->setEnabled(selectedIndex >= 0); + m_d->ui->btnRemove->setEnabled(found); + m_d->ui->btnTransparent->setEnabled(found); - if (selectedIndex < 0) { + if (!found) { KisSignalsBlocker b(m_d->ui->btnTransparent); m_d->ui->btnTransparent->setChecked(false); } - QModelIndex newIndex = - selectedIndex >= 0 ? - m_d->colorModel->indexFromId(selectedIndex) : QModelIndex(); + QModelIndex newIndex = found ? candidateIdx : QModelIndex(); - if (newIndex != m_d->ui->colorView->currentIndex()) { + 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->colorSet.clear(); + 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->colorSet.add(KoColorSetEntry(colors.colors[i], name)); + m_d->colorModel->addEntry(KisSwatch(colors.colors[i], name)); } } - m_d->colorModel->setColorSet(&m_d->colorSet); 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; - const int activeIndex = m_d->colorModel->idFromIndex(index); - KIS_ASSERT_RECOVER_RETURN(activeIndex >= 0); + int activeIndex = -1; KisColorizeMask::KeyStrokeColors colors; - for (quint32 i = 0; i < m_d->colorSet.nColors(); i++) { - colors.colors << m_d->colorSet.getColorGlobal(i).color(); + 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 int activeIndex = m_d->colorModel->idFromIndex(index); - KIS_ASSERT_RECOVER_RETURN(activeIndex >= 0); - - - const KoColor color = m_d->colorSet.getColorGlobal((quint32)activeIndex).color(); + 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); } diff --git a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.h b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.h index 8512f6f533..36aecbf802 100644 --- a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.h +++ b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.h @@ -1,71 +1,74 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TOOL_LAZY_BRUSH_OPTIONS_WIDGET_H #define __KIS_TOOL_LAZY_BRUSH_OPTIONS_WIDGET_H #include #include #include #include "kis_types.h" class KisCanvasResourceProvider; class KoColor; +/** + * @brief The KisToolLazyBrushOptionsWidget class + */ class KisToolLazyBrushOptionsWidget : public QWidget { Q_OBJECT public: KisToolLazyBrushOptionsWidget(KisCanvasResourceProvider *provider, QWidget *parent); ~KisToolLazyBrushOptionsWidget() override; private Q_SLOTS: void entrySelected(QModelIndex index); void slotCurrentFgColorChanged(const KoColor &color); void slotCurrentNodeChanged(KisNodeSP node); void slotColorLabelsChanged(); void slotMakeTransparent(bool value); void slotRemove(); void slotUpdate(); void slotSetAutoUpdates(bool value); void slotSetShowKeyStrokes(bool value); void slotSetShowOutput(bool value); void slotUseEdgeDetectionChanged(bool value); void slotEdgeDetectionSizeChanged(int value); void slotRadiusChanged(int value); void slotCleanUpChanged(int value); void slotLimitToDeviceChanged(bool value); void slotUpdateNodeProperties(); protected: void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_TOOL_LAZY_BRUSH_OPTIONS_WIDGET_H */