diff --git a/libs/flake/KoFilterEffectLoadingContext.h b/libs/flake/KoFilterEffectLoadingContext.h
index f1d765867a..cffc6e16b5 100644
--- a/libs/flake/KoFilterEffectLoadingContext.h
+++ b/libs/flake/KoFilterEffectLoadingContext.h
@@ -1,84 +1,84 @@
/* This file is part of the KDE project
* Copyright (c) 2010 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
* Library 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 KOFILTEREFFECTLOADINGCONTEXT_H
#define KOFILTEREFFECTLOADINGCONTEXT_H
#include "kritaflake_export.h"
class QRectF;
class QPointF;
#include
-#include
+#include
/// This class provides a loading context for filter effects
class KRITAFLAKE_EXPORT KoFilterEffectLoadingContext
{
public:
/**
* Constructs a new filter effect loading context
* @param basePath the xml document base path
*/
explicit KoFilterEffectLoadingContext(const QString &basePath = QString());
/// Destructor
virtual ~KoFilterEffectLoadingContext();
/**
* Sets the bounding box of the shape a filter is loaded for.
* The shapes bounding box is used to convert from user space
* coordinates to bounding box coordinates for filter attributes.
* @param shapeBound the shapes bounding box
*/
void setShapeBoundingBox(const QRectF &shapeBound);
/// Enables conversion of filter units
void enableFilterUnitsConversion(bool enable);
/// Enables conversion of filter primitive units
void enableFilterPrimitiveUnitsConversion(bool enable);
/// Converts a point value from user space to bounding box coordinates
QPointF convertFilterUnits(const QPointF &value) const;
/// Converts an x value from user space to bounding box coordinates
qreal convertFilterUnitsX(qreal value) const;
/// Converts an y value from user space to bounding box coordinates
qreal convertFilterUnitsY(qreal value) const;
QPointF convertFilterPrimitiveUnits(const QPointF &value) const;
/// Converts an x value from user space to bounding box coordinates
qreal convertFilterPrimitiveUnitsX(qreal value) const;
/// Converts an y value from user space to bounding box coordinates
qreal convertFilterPrimitiveUnitsY(qreal value) const;
/// Converts a href to an absolute path name
QString pathFromHref(const QString &href) const;
private:
class Private;
Private * const d;
};
#endif // KOFILTEREFFECTLOADINGCONTEXT_H
diff --git a/libs/pigment/resources/KoColorSet.cpp b/libs/pigment/resources/KoColorSet.cpp
index 4267c95bcb..d571ac7abd 100644
--- a/libs/pigment/resources/KoColorSet.cpp
+++ b/libs/pigment/resources/KoColorSet.cpp
@@ -1,1610 +1,1608 @@
/* This file is part of the KDE project
Copyright (c) 2005 Boudewijn Rempt
Copyright (c) 2016 L. E. Segovia
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
-#include
#include
#include
#include // qFromLittleEndian
#include
#include
#include
#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(this))
{
if (!filename.isEmpty()) {
QFileInfo f(filename);
setIsEditable(f.isWritable());
}
}
/// Create an copied palette
KoColorSet::KoColorSet(const KoColorSet& rhs)
: QObject(Q_NULLPTR)
, KoResource(rhs)
, d(new Private(this))
{
d->paletteType = rhs.d->paletteType;
d->data = rhs.d->data;
d->comment = rhs.d->comment;
d->groupNames = rhs.d->groupNames;
d->groups = rhs.d->groups;
d->isGlobal = rhs.d->isGlobal;
d->isEditable = rhs.d->isEditable;
}
KoColorSet::~KoColorSet()
{ }
bool KoColorSet::load()
{
QFile file(filename());
if (file.size() == 0) return false;
if (!file.open(QIODevice::ReadOnly)) {
warnPigment << "Can't open file " << filename();
return false;
}
bool res = loadFromDevice(&file);
file.close();
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 d->init();
}
bool KoColorSet::save()
{
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
}
}
bool KoColorSet::saveToDevice(QIODevice *dev) const
{
bool res;
switch(d->paletteType) {
case GPL:
res = d->saveGpl(dev);
break;
default:
res = d->saveKpl(dev);
}
if (res) {
KoResource::saveToDevice(dev);
}
return res;
}
QByteArray KoColorSet::toByteArray() const
{
QBuffer s;
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 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:
suffix = ".gpl";
break;
case ACT:
suffix = ".act";
break;
case RIFF_PAL:
case PSP_PAL:
suffix = ".pal";
break;
case ACO:
suffix = ".aco";
break;
case XML:
suffix = ".xml";
break;
case KPL:
suffix = ".kpl";
break;
case SBZ:
suffix = ".sbz";
break;
default:
suffix = defaultFileExtension();
}
QStringList fileName = filename().split(".");
fileName.last() = suffix.replace(".", "");
setFilename(fileName.join("."));
}
quint32 KoColorSet::colorCount() const
{
int colorCount = d->groups[GLOBAL_GROUP_NAME].colorCount();
for (KisSwatchGroup &g : d->groups.values()) {
colorCount += g.colorCount();
}
return colorCount;
}
void KoColorSet::add(const KisSwatch &c, const QString &groupName)
{
KisSwatchGroup &modifiedGroup = d->groups.contains(groupName)
? d->groups[groupName] : d->global();
modifiedGroup.addEntry(c);
}
void KoColorSet::setEntry(const KisSwatch &e, int x, int y, const QString &groupName)
{
KisSwatchGroup &modifiedGroup = d->groups.contains(groupName)
? d->groups[groupName] : d->global();
modifiedGroup.setEntry(e, x, y);
}
void KoColorSet::clear()
{
d->groups.clear();
d->groupNames.clear();
d->groups[GLOBAL_GROUP_NAME] = KisSwatchGroup();
d->groupNames.append(GLOBAL_GROUP_NAME);
}
KisSwatch KoColorSet::getColorGlobal(quint32 x, quint32 y) const
{
int yInGroup = y;
QString nameGroupFoundIn;
for (const QString &groupName : d->groupNames) {
if (yInGroup < d->groups[groupName].rowCount()) {
nameGroupFoundIn = groupName;
break;
} else {
yInGroup -= d->groups[groupName].rowCount();
}
}
const KisSwatchGroup &groupFoundIn = nameGroupFoundIn == GLOBAL_GROUP_NAME
? d->global() : d->groups[nameGroupFoundIn];
Q_ASSERT(groupFoundIn.checkEntry(x, yInGroup));
return groupFoundIn.getEntry(x, yInGroup);
}
KisSwatch KoColorSet::getColorGroup(quint32 x, quint32 y, QString groupName)
{
KisSwatch e;
const KisSwatchGroup &sourceGroup = groupName == QString()
? d->global() : d->groups[groupName];
if (sourceGroup.checkEntry(x, y)) {
e = sourceGroup.getEntry(x, y);
}
return e;
}
QStringList KoColorSet::getGroupNames()
{
if (d->groupNames.size() != d->groups.size()) {
warnPigment << "mismatch between groups and the groupnames list.";
return QStringList(d->groups.keys());
}
return d->groupNames;
}
bool KoColorSet::changeGroupName(const QString &oldGroupName, const QString &newGroupName)
{
if (!d->groups.contains(oldGroupName)) {
return false;
}
if (oldGroupName == newGroupName) {
return true;
}
d->groups[newGroupName] = d->groups[oldGroupName];
d->groups.remove(oldGroupName);
d->groups[newGroupName].setName(newGroupName);
//rename the string in the stringlist;
int index = d->groupNames.indexOf(oldGroupName);
d->groupNames.replace(index, newGroupName);
return true;
}
void KoColorSet::setColumnCount(int columns)
{
d->groups[GLOBAL_GROUP_NAME].setColumnCount(columns);
for (KisSwatchGroup &g : d->groups.values()) {
g.setColumnCount(columns);
}
}
int KoColorSet::columnCount() const
{
return d->groups[GLOBAL_GROUP_NAME].columnCount();
}
QString KoColorSet::comment()
{
return d->comment;
}
void KoColorSet::setComment(QString comment)
{
d->comment = comment;
}
bool KoColorSet::addGroup(const QString &groupName)
{
if (d->groups.contains(groupName) || d->groupNames.contains(groupName)) {
return false;
}
d->groupNames.append(groupName);
d->groups[groupName] = KisSwatchGroup();
d->groups[groupName].setName(groupName);
return true;
}
bool KoColorSet::moveGroup(const QString &groupName, const QString &groupNameInsertBefore)
{
if (d->groupNames.contains(groupName)==false || d->groupNames.contains(groupNameInsertBefore)==false) {
return false;
}
if (groupNameInsertBefore != GLOBAL_GROUP_NAME && groupName != GLOBAL_GROUP_NAME) {
d->groupNames.removeAt(d->groupNames.indexOf(groupName));
int index = d->groupNames.indexOf(groupNameInsertBefore);
d->groupNames.insert(index, groupName);
}
return true;
}
bool KoColorSet::removeGroup(const QString &groupName, bool keepColors)
{
if (!d->groups.contains(groupName)) {
return false;
}
if (groupName == GLOBAL_GROUP_NAME) {
return false;
}
if (keepColors) {
// put all colors directly below global
int startingRow = d->groups[GLOBAL_GROUP_NAME].rowCount();
for (const KisSwatchGroup::SwatchInfo &info : d->groups[groupName].infoList()) {
d->groups[GLOBAL_GROUP_NAME].setEntry(info.swatch,
info.column,
info.row + startingRow);
}
}
d->groupNames.removeAt(d->groupNames.indexOf(groupName));
d->groups.remove(groupName);
return true;
}
QString KoColorSet::defaultFileExtension() const
{
return QString(".kpl");
}
int KoColorSet::rowCount() const
{
int res = 0;
for (const QString &name : d->groupNames) {
res += d->groups[name].rowCount();
}
return res;
}
KisSwatchGroup *KoColorSet::getGroup(const QString &name)
{
if (!d->groups.contains(name)) {
return Q_NULLPTR;
}
return &(d->groups[name]);
}
KisSwatchGroup *KoColorSet::getGlobalGroup()
{
return getGroup(GLOBAL_GROUP_NAME);
}
bool KoColorSet::isGlobal() const
{
return d->isGlobal;
}
void KoColorSet::setIsGlobal(bool isGlobal)
{
d->isGlobal = isGlobal;
}
bool KoColorSet::isEditable() const
{
return d->isEditable;
}
void KoColorSet::setIsEditable(bool isEditable)
{
d->isEditable = isEditable;
}
KisSwatchGroup::SwatchInfo KoColorSet::getClosestColorInfo(KoColor compare, bool useGivenColorSpace)
{
KisSwatchGroup::SwatchInfo res;
quint8 highestPercentage = 0;
quint8 testPercentage = 0;
for (const QString &groupName : getGroupNames()) {
KisSwatchGroup *group = getGroup(groupName);
for (const KisSwatchGroup::SwatchInfo &currInfo : group->infoList()) {
KoColor color = currInfo.swatch.color();
if (useGivenColorSpace == true && compare.colorSpace() != color.colorSpace()) {
color.convertTo(compare.colorSpace());
} else if (compare.colorSpace() != color.colorSpace()) {
compare.convertTo(color.colorSpace());
}
testPercentage = (255 - compare.colorSpace()->difference(compare.data(), color.data()));
if (testPercentage > highestPercentage)
{
highestPercentage = testPercentage;
res = currInfo;
}
}
}
return res;
}
/********************************KoColorSet::Private**************************/
KoColorSet::Private::Private(KoColorSet *a_colorSet)
: colorSet(a_colorSet)
, isGlobal(true)
, isEditable(false)
{
groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup();
groupNames.append(KoColorSet::GLOBAL_GROUP_NAME);
}
KoColorSet::PaletteType KoColorSet::Private::detectFormat(const QString &fileName, const QByteArray &ba)
{
QFileInfo fi(fileName);
// .pal
if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) {
return KoColorSet::RIFF_PAL;
}
// .gpl
else if (ba.startsWith("GIMP Palette")) {
return KoColorSet::GPL;
}
// .pal
else if (ba.startsWith("JASC-PAL")) {
return KoColorSet::PSP_PAL;
}
else if (fi.suffix().toLower() == "aco") {
return KoColorSet::ACO;
}
else if (fi.suffix().toLower() == "act") {
return KoColorSet::ACT;
}
else if (fi.suffix().toLower() == "xml") {
return KoColorSet::XML;
}
else if (fi.suffix().toLower() == "kpl") {
return KoColorSet::KPL;
}
else if (fi.suffix().toLower() == "sbz") {
return KoColorSet::SBZ;
}
return KoColorSet::UNKNOWN;
}
void KoColorSet::Private::scribusParseColor(KoColorSet *set, QXmlStreamReader *xml)
{
KisSwatch colorEntry;
// It's a color, retrieve it
QXmlStreamAttributes colorProperties = xml->attributes();
QStringRef colorName = colorProperties.value("NAME");
colorEntry.setName(colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString());
// RGB or CMYK?
if (colorProperties.hasAttribute("RGB")) {
dbgPigment << "Color " << colorProperties.value("NAME") << ", RGB " << colorProperties.value("RGB");
KoColor currentColor(KoColorSpaceRegistry::instance()->rgb8());
QStringRef colorValue = colorProperties.value("RGB");
if (colorValue.length() != 7 && colorValue.at(0) != '#') { // Color is a hexadecimal number
xml->raiseError("Invalid rgb8 color (malformed): " + colorValue);
return;
} else {
bool rgbOk;
quint32 rgb = colorValue.mid(1).toUInt(&rgbOk, 16);
if (!rgbOk) {
xml->raiseError("Invalid rgb8 color (unable to convert): " + colorValue);
return;
}
quint8 r = rgb >> 16 & 0xff;
quint8 g = rgb >> 8 & 0xff;
quint8 b = rgb & 0xff;
dbgPigment << "Color parsed: "<< r << g << b;
currentColor.data()[0] = r;
currentColor.data()[1] = g;
currentColor.data()[2] = b;
currentColor.setOpacity(OPACITY_OPAQUE_U8);
colorEntry.setColor(currentColor);
set->add(colorEntry);
while(xml->readNextStartElement()) {
//ignore - these are all unknown or the /> element tag
xml->skipCurrentElement();
}
return;
}
}
else if (colorProperties.hasAttribute("CMYK")) {
dbgPigment << "Color " << colorProperties.value("NAME") << ", CMYK " << colorProperties.value("CMYK");
KoColor currentColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString()));
QStringRef colorValue = colorProperties.value("CMYK");
if (colorValue.length() != 9 && colorValue.at(0) != '#') { // Color is a hexadecimal number
xml->raiseError("Invalid cmyk color (malformed): " % colorValue);
return;
}
else {
bool cmykOk;
quint32 cmyk = colorValue.mid(1).toUInt(&cmykOk, 16); // cmyk uses the full 32 bits
if (!cmykOk) {
xml->raiseError("Invalid cmyk color (unable to convert): " % colorValue);
return;
}
quint8 c = cmyk >> 24 & 0xff;
quint8 m = cmyk >> 16 & 0xff;
quint8 y = cmyk >> 8 & 0xff;
quint8 k = cmyk & 0xff;
dbgPigment << "Color parsed: "<< c << m << y << k;
currentColor.data()[0] = c;
currentColor.data()[1] = m;
currentColor.data()[2] = y;
currentColor.data()[3] = k;
currentColor.setOpacity(OPACITY_OPAQUE_U8);
colorEntry.setColor(currentColor);
set->add(colorEntry);
while(xml->readNextStartElement()) {
//ignore - these are all unknown or the /> element tag
xml->skipCurrentElement();
}
return;
}
}
else {
xml->raiseError("Unknown color space for color " + colorEntry.name());
}
}
bool KoColorSet::Private::loadScribusXmlPalette(KoColorSet *set, QXmlStreamReader *xml)
{
//1. Get name
QXmlStreamAttributes paletteProperties = xml->attributes();
QStringRef paletteName = paletteProperties.value("Name");
dbgPigment << "Processed name of palette:" << paletteName;
set->setName(paletteName.toString());
//2. Inside the SCRIBUSCOLORS, there are lots of colors. Retrieve them
while(xml->readNextStartElement()) {
QStringRef currentElement = xml->name();
if(QStringRef::compare(currentElement, "COLOR", Qt::CaseInsensitive) == 0) {
scribusParseColor(set, xml);
}
else {
xml->skipCurrentElement();
}
}
if(xml->hasError()) {
return false;
}
return true;
}
quint16 KoColorSet::Private::readShort(QIODevice *io) {
quint16 val;
quint64 read = io->read((char*)&val, 2);
if (read != 2) return false;
return qFromBigEndian(val);
}
bool KoColorSet::Private::init()
{
// just in case this is a reload (eg by KoEditColorSetDialog),
groupNames.clear();
groups.clear();
groupNames.append(KoColorSet::GLOBAL_GROUP_NAME);
groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup();
if (colorSet->filename().isNull()) {
warnPigment << "Cannot load palette" << colorSet->name() << "there is no filename set";
return false;
}
if (data.isNull()) {
QFile file(colorSet->filename());
if (file.size() == 0) {
warnPigment << "Cannot load palette" << colorSet->name() << "there is no data available";
return false;
}
file.open(QIODevice::ReadOnly);
data = file.readAll();
file.close();
}
bool res = false;
paletteType = detectFormat(colorSet->filename(), data);
switch(paletteType) {
case GPL:
res = loadGpl();
break;
case ACT:
res = loadAct();
break;
case RIFF_PAL:
res = loadRiff();
break;
case PSP_PAL:
res = loadPsp();
break;
case ACO:
res = loadAco();
break;
case XML:
res = loadXml();
break;
case KPL:
res = loadKpl();
break;
case SBZ:
res = loadSbz();
break;
default:
res = false;
}
colorSet->setValid(res);
QImage img(global().columnCount() * 4, global().rowCount() * 4, QImage::Format_ARGB32);
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);
data.clear();
return res;
}
bool KoColorSet::Private::saveGpl(QIODevice *dev) const
{
Q_ASSERT(dev->isOpen());
Q_ASSERT(dev->isWritable());
QTextStream stream(dev);
stream << "GIMP Palette\nName: " << colorSet->name() << "\nColumns: " << colorSet->columnCount() << "\n#\n";
/*
* Qt doesn't provide an interface to get a const reference to a QHash, that is
* the underlying data structure of groups. Therefore, directly use
* groups[KoColorSet::GLOBAL_GROUP_NAME] so that saveGpl can stay const
*/
for (int y = 0; y < groups[KoColorSet::GLOBAL_GROUP_NAME].rowCount(); y++) {
for (int x = 0; x < colorSet->columnCount(); x++) {
if (!groups[KoColorSet::GLOBAL_GROUP_NAME].checkEntry(x, y)) {
continue;
}
const KisSwatch& entry = groups[KoColorSet::GLOBAL_GROUP_NAME].getEntry(x, y);
QColor c = entry.color().toQColor();
stream << c.red() << " " << c.green() << " " << c.blue() << "\t";
if (entry.name().isEmpty())
stream << "Untitled\n";
else
stream << entry.name() << "\n";
}
}
return true;
}
bool KoColorSet::Private::loadGpl()
{
QString s = QString::fromUtf8(data.data(), data.count());
if (s.isEmpty() || s.isNull() || s.length() < 50) {
warnPigment << "Illegal Gimp palette file: " << colorSet->filename();
return false;
}
quint32 index = 0;
QStringList lines = s.split('\n', QString::SkipEmptyParts);
if (lines.size() < 3) {
warnPigment << "Not enough lines in palette file: " << colorSet->filename();
return false;
}
QString columnsText;
qint32 r, g, b;
KisSwatch e;
// Read name
if (!lines[0].startsWith("GIMP") || !lines[1].toLower().contains("name")) {
warnPigment << "Illegal Gimp palette file: " << colorSet->filename();
return false;
}
colorSet->setName(i18n(lines[1].split(":")[1].trimmed().toLatin1()));
index = 2;
// Read columns
int columns = 0;
if (lines[index].toLower().contains("columns")) {
columnsText = lines[index].split(":")[1].trimmed();
columns = columnsText.toInt();
global().setColumnCount(columns);
index = 3;
}
for (qint32 i = index; i < lines.size(); i++) {
if (lines[i].startsWith('#')) {
comment += lines[i].mid(1).trimmed() + ' ';
} else if (!lines[i].isEmpty()) {
QStringList a = lines[i].replace('\t', ' ').split(' ', QString::SkipEmptyParts);
if (a.count() < 3) {
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;
}
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);
}
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::Private::loadPsp()
{
QFileInfo info(colorSet->filename());
colorSet->setName(info.baseName());
KisSwatch e;
qint32 r, g, b;
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;
int entries = l[2].toInt();
for (int i = 0; i < entries; ++i) {
QStringList a = l[i + 3].replace('\t', ' ').split(' ', QString::SkipEmptyParts);
if (a.count() != 3) {
continue;
}
r = qBound(0, a[0].toInt(), 255);
g = qBound(0, a[1].toInt(), 255);
b = qBound(0, a[2].toInt(), 255);
e.setColor(KoColor(QColor(r, g, b),
KoColorSpaceRegistry::instance()->rgb8()));
QString name = a.join(" ");
e.setName(name.isEmpty() ? i18n("Untitled") : name);
groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e);
}
return true;
}
bool KoColorSet::Private::loadKpl()
{
QBuffer buf(&data);
buf.open(QBuffer::ReadOnly);
QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "krita/x-colorset", KoStore::Zip));
if (!store || store->bad()) { return false; }
if (store->hasFile("profiles.xml")) {
if (!store->open("profiles.xml")) { return false; }
QByteArray data;
data.resize(store->size());
QByteArray ba = store->read(store->size());
store->close();
QDomDocument doc;
doc.setContent(ba);
QDomElement e = doc.documentElement();
QDomElement c = e.firstChildElement(KPL_PALETTE_PROFILE_TAG);
while (!c.isNull()) {
QString name = c.attribute(KPL_PALETTE_NAME_ATTR);
QString filename = c.attribute(KPL_PALETTE_FILENAME_ATTR);
QString colorModelId = c.attribute(KPL_COLOR_MODEL_ID_ATTR);
QString colorDepthId = c.attribute(KPL_COLOR_DEPTH_ID_ATTR);
if (!KoColorSpaceRegistry::instance()->profileByName(name)) {
store->open(filename);
QByteArray data;
data.resize(store->size());
data = store->read(store->size());
store->close();
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(colorModelId, colorDepthId, data);
if (profile && profile->valid()) {
KoColorSpaceRegistry::instance()->addProfile(profile);
}
}
c = c.nextSiblingElement();
}
}
{
if (!store->open("colorset.xml")) { return false; }
QByteArray data;
data.resize(store->size());
QByteArray ba = store->read(store->size());
store->close();
QDomDocument doc;
doc.setContent(ba);
QDomElement e = doc.documentElement();
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);
loadKplGroup(doc, e, colorSet->getGlobalGroup());
QDomElement g = e.firstChildElement(KPL_GROUP_TAG);
while (!g.isNull()) {
QString groupName = g.attribute(KPL_GROUP_NAME_ATTR);
colorSet->addGroup(groupName);
loadKplGroup(doc, g, colorSet->getGroup(groupName));
g = g.nextSiblingElement(KPL_GROUP_TAG);
}
}
buf.close();
return true;
}
bool KoColorSet::Private::loadAco()
{
QFileInfo info(colorSet->filename());
colorSet->setName(info.baseName());
QBuffer buf(&data);
buf.open(QBuffer::ReadOnly);
quint16 version = readShort(&buf);
quint16 numColors = readShort(&buf);
KisSwatch e;
if (version == 1 && buf.size() > 4+numColors*10) {
buf.seek(4+numColors*10);
version = readShort(&buf);
numColors = readShort(&buf);
}
const quint16 quint16_MAX = 65535;
for (int i = 0; i < numColors && !buf.atEnd(); ++i) {
quint16 colorSpace = readShort(&buf);
quint16 ch1 = readShort(&buf);
quint16 ch2 = readShort(&buf);
quint16 ch3 = readShort(&buf);
quint16 ch4 = readShort(&buf);
bool skip = false;
if (colorSpace == 0) { // RGB
const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile();
KoColor c(KoColorSpaceRegistry::instance()->rgb16(srgb));
reinterpret_cast(c.data())[0] = ch3;
reinterpret_cast(c.data())[1] = ch2;
reinterpret_cast(c.data())[2] = ch1;
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else if (colorSpace == 1) { // HSB
QColor qc;
qc.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0);
KoColor c(qc, KoColorSpaceRegistry::instance()->rgb16());
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else if (colorSpace == 2) { // CMYK
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), QString()));
reinterpret_cast(c.data())[0] = quint16_MAX - ch1;
reinterpret_cast(c.data())[1] = quint16_MAX - ch2;
reinterpret_cast(c.data())[2] = quint16_MAX - ch3;
reinterpret_cast(c.data())[3] = quint16_MAX - ch4;
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else if (colorSpace == 7) { // LAB
KoColor c = KoColor(KoColorSpaceRegistry::instance()->lab16());
reinterpret_cast(c.data())[0] = ch3;
reinterpret_cast(c.data())[1] = ch2;
reinterpret_cast(c.data())[2] = ch1;
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else if (colorSpace == 8) { // GRAY
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), QString()));
reinterpret_cast(c.data())[0] = ch1 * (quint16_MAX / 10000);
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else {
warnPigment << "Unsupported colorspace in palette" << colorSet->filename() << "(" << colorSpace << ")";
skip = true;
}
if (version == 2) {
quint16 v2 = readShort(&buf); //this isn't a version, it's a marker and needs to be skipped.
Q_UNUSED(v2);
quint16 size = readShort(&buf) -1; //then comes the length
if (size>0) {
QByteArray ba = buf.read(size*2);
if (ba.size() == size*2) {
QTextCodec *Utf16Codec = QTextCodec::codecForName("UTF-16BE");
e.setName(Utf16Codec->toUnicode(ba));
} else {
warnPigment << "Version 2 name block is the wrong size" << colorSet->filename();
}
}
v2 = readShort(&buf); //end marker also needs to be skipped.
Q_UNUSED(v2);
}
if (!skip) {
groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e);
}
}
return true;
}
bool KoColorSet::Private::loadSbz() {
QBuffer buf(&data);
buf.open(QBuffer::ReadOnly);
// &buf is a subclass of QIODevice
QScopedPointer store(KoStore::createStore(&buf, KoStore::Read, "application/x-swatchbook", KoStore::Zip));
if (!store || store->bad()) return false;
if (store->hasFile("swatchbook.xml")) { // Try opening...
if (!store->open("swatchbook.xml")) { return false; }
QByteArray data;
data.resize(store->size());
QByteArray ba = store->read(store->size());
store->close();
dbgPigment << "XML palette: " << colorSet->filename() << ", SwatchBooker format";
QDomDocument doc;
int errorLine, errorColumn;
QString errorMessage;
bool status = doc.setContent(ba, &errorMessage, &errorLine, &errorColumn);
if (!status) {
warnPigment << "Illegal XML palette:" << colorSet->filename();
warnPigment << "Error (line" << errorLine << ", column" << errorColumn << "):" << errorMessage;
return false;
}
QDomElement e = doc.documentElement(); // SwatchBook
// Start reading properties...
QDomElement metadata = e.firstChildElement("metadata");
if (e.isNull()) {
warnPigment << "Palette metadata not found";
return false;
}
QDomElement title = metadata.firstChildElement("dc:title");
QString colorName = title.text();
colorName = colorName.isEmpty() ? i18n("Untitled") : colorName;
colorSet->setName(colorName);
dbgPigment << "Processed name of palette:" << colorSet->name();
// End reading properties
// Now read colors...
QDomElement materials = e.firstChildElement("materials");
if (materials.isNull()) {
warnPigment << "Materials (color definitions) not found";
return false;
}
// This one has lots of "color" elements
QDomElement colorElement = materials.firstChildElement("color");
if (colorElement.isNull()) {
warnPigment << "Color definitions not found (line" << materials.lineNumber() << ", column" << materials.columnNumber() << ")";
return false;
}
// Also read the swatch book...
QDomElement book = e.firstChildElement("book");
if (book.isNull()) {
warnPigment << "Palette book (swatch composition) not found (line" << e.lineNumber() << ", column" << e.columnNumber() << ")";
return false;
}
// Which has lots of "swatch"es (todo: support groups)
QDomElement swatch = book.firstChildElement();
if (swatch.isNull()) {
warnPigment << "Swatches/groups definition not found (line" << book.lineNumber() << ", column" << book.columnNumber() << ")";
return false;
}
// We'll store colors here, and as we process swatches
// we'll add them to the palette
QHash materialsBook;
QHash fileColorSpaces;
// Color processing
for(; !colorElement.isNull(); colorElement = colorElement.nextSiblingElement("color"))
{
KisSwatch currentEntry;
// Set if color is spot
currentEntry.setSpotColor(colorElement.attribute("usage") == "spot");
// inside contains id and name
// one or more define the color
QDomElement currentColorMetadata = colorElement.firstChildElement("metadata");
QDomNodeList currentColorValues = colorElement.elementsByTagName("values");
// Get color name
QDomElement colorTitle = currentColorMetadata.firstChildElement("dc:title");
QDomElement colorId = currentColorMetadata.firstChildElement("dc:identifier");
// Is there an id? (we need that at the very least for identifying a color)
if (colorId.text().isEmpty()) {
warnPigment << "Unidentified color (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")";
return false;
}
if (materialsBook.contains(colorId.text())) {
warnPigment << "Duplicated color definition (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")";
return false;
}
// Get a valid color name
currentEntry.setId(colorId.text());
currentEntry.setName(colorTitle.text().isEmpty() ? colorId.text() : colorTitle.text());
// Get a valid color definition
if (currentColorValues.isEmpty()) {
warnPigment << "Color definitions not found (line" << colorElement.lineNumber() << ", column" << colorElement.columnNumber() << ")";
return false;
}
bool firstDefinition = false;
const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile();
// Priority: Lab, otherwise the first definition found
for(int j = 0; j < currentColorValues.size(); j++) {
QDomNode colorValue = currentColorValues.at(j);
QDomElement colorValueE = colorValue.toElement();
QString model = colorValueE.attribute("model", QString());
// sRGB,RGB,HSV,HSL,CMY,CMYK,nCLR: 0 -> 1
// YIQ: Y 0 -> 1 : IQ -0.5 -> 0.5
// Lab: L 0 -> 100 : ab -128 -> 127
// XYZ: 0 -> ~100
if (model == "Lab") {
QStringList lab = colorValueE.text().split(" ");
if (lab.length() != 3) {
warnPigment << "Invalid Lab color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float l = lab.at(0).toFloat(&status);
float a = lab.at(1).toFloat(&status);
float b = lab.at(2).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), QString()));
reinterpret_cast(c.data())[0] = l;
reinterpret_cast(c.data())[1] = a;
reinterpret_cast(c.data())[2] = b;
c.setOpacity(OPACITY_OPAQUE_F);
firstDefinition = true;
currentEntry.setColor(c);
break; // Immediately add this one
}
else if (model == "sRGB" && !firstDefinition) {
QStringList rgb = colorValueE.text().split(" ");
if (rgb.length() != 3) {
warnPigment << "Invalid sRGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float r = rgb.at(0).toFloat(&status);
float g = rgb.at(1).toFloat(&status);
float b = rgb.at(2).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb));
reinterpret_cast(c.data())[0] = r;
reinterpret_cast(c.data())[1] = g;
reinterpret_cast(c.data())[2] = b;
c.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(c);
firstDefinition = true;
}
else if (model == "XYZ" && !firstDefinition) {
QStringList xyz = colorValueE.text().split(" ");
if (xyz.length() != 3) {
warnPigment << "Invalid XYZ color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float x = xyz.at(0).toFloat(&status);
float y = xyz.at(1).toFloat(&status);
float z = xyz.at(2).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Float32BitsColorDepthID.id(), QString()));
reinterpret_cast(c.data())[0] = x;
reinterpret_cast(c.data())[1] = y;
reinterpret_cast(c.data())[2] = z;
c.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(c);
firstDefinition = true;
}
// The following color spaces admit an ICC profile (in SwatchBooker)
else if (model == "CMYK" && !firstDefinition) {
QStringList cmyk = colorValueE.text().split(" ");
if (cmyk.length() != 4) {
warnPigment << "Invalid CMYK color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float c = cmyk.at(0).toFloat(&status);
float m = cmyk.at(1).toFloat(&status);
float y = cmyk.at(2).toFloat(&status);
float k = cmyk.at(3).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
QString space = colorValueE.attribute("space");
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), QString());
if (!space.isEmpty()) {
// Try loading the profile and add it to the registry
if (!fileColorSpaces.contains(space)) {
store->enterDirectory("profiles");
store->open(space);
QByteArray data;
data.resize(store->size());
data = store->read(store->size());
store->close();
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data);
if (profile && profile->valid()) {
KoColorSpaceRegistry::instance()->addProfile(profile);
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile);
fileColorSpaces.insert(space, colorSpace);
}
}
else {
colorSpace = fileColorSpaces.value(space);
}
}
KoColor color(colorSpace);
reinterpret_cast(color.data())[0] = c;
reinterpret_cast(color.data())[1] = m;
reinterpret_cast(color.data())[2] = y;
reinterpret_cast(color.data())[3] = k;
color.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(color);
firstDefinition = true;
}
else if (model == "GRAY" && !firstDefinition) {
QString gray = colorValueE.text();
float g = gray.toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
QString space = colorValueE.attribute("space");
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Float32BitsColorDepthID.id(), QString());
if (!space.isEmpty()) {
// Try loading the profile and add it to the registry
if (!fileColorSpaces.contains(space)) {
store->enterDirectory("profiles");
store->open(space);
QByteArray data;
data.resize(store->size());
data = store->read(store->size());
store->close();
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data);
if (profile && profile->valid()) {
KoColorSpaceRegistry::instance()->addProfile(profile);
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile);
fileColorSpaces.insert(space, colorSpace);
}
}
else {
colorSpace = fileColorSpaces.value(space);
}
}
KoColor c(colorSpace);
reinterpret_cast(c.data())[0] = g;
c.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(c);
firstDefinition = true;
}
else if (model == "RGB" && !firstDefinition) {
QStringList rgb = colorValueE.text().split(" ");
if (rgb.length() != 3) {
warnPigment << "Invalid RGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float r = rgb.at(0).toFloat(&status);
float g = rgb.at(1).toFloat(&status);
float b = rgb.at(2).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
QString space = colorValueE.attribute("space");
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb);
if (!space.isEmpty()) {
// Try loading the profile and add it to the registry
if (!fileColorSpaces.contains(space)) {
store->enterDirectory("profiles");
store->open(space);
QByteArray data;
data.resize(store->size());
data = store->read(store->size());
store->close();
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), data);
if (profile && profile->valid()) {
KoColorSpaceRegistry::instance()->addProfile(profile);
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), profile);
fileColorSpaces.insert(space, colorSpace);
}
}
else {
colorSpace = fileColorSpaces.value(space);
}
}
KoColor c(colorSpace);
reinterpret_cast(c.data())[0] = r;
reinterpret_cast(c.data())[1] = g;
reinterpret_cast(c.data())[2] = b;
c.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(c);
firstDefinition = true;
}
else {
warnPigment << "Color space not implemented:" << model << "(line" << colorValueE.lineNumber() << ", column "<< colorValueE.columnNumber() << ")";
}
}
if (firstDefinition) {
materialsBook.insert(currentEntry.id(), currentEntry);
}
else {
warnPigment << "No supported color spaces for the current color (line" << colorElement.lineNumber() << ", column "<< colorElement.columnNumber() << ")";
return false;
}
}
// End colors
// Now decide which ones will go into the palette
for(;!swatch.isNull(); swatch = swatch.nextSiblingElement()) {
QString type = swatch.tagName();
if (type.isEmpty() || type.isNull()) {
warnPigment << "Invalid swatch/group definition (no id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")";
return false;
}
else if (type == "swatch") {
QString id = swatch.attribute("material");
if (id.isEmpty() || id.isNull()) {
warnPigment << "Invalid swatch definition (no material id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")";
return false;
}
if (materialsBook.contains(id)) {
groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(materialsBook.value(id));
}
else {
warnPigment << "Invalid swatch definition (material not found) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")";
return false;
}
}
else if (type == "group") {
QDomElement groupMetadata = swatch.firstChildElement("metadata");
if (groupMetadata.isNull()) {
warnPigment << "Invalid group definition (missing metadata) (line" << groupMetadata.lineNumber() << ", column" << groupMetadata.columnNumber() << ")";
return false;
}
QDomElement groupTitle = metadata.firstChildElement("dc:title");
if (groupTitle.isNull()) {
warnPigment << "Invalid group definition (missing title) (line" << groupTitle.lineNumber() << ", column" << groupTitle.columnNumber() << ")";
return false;
}
QString currentGroupName = groupTitle.text();
QDomElement groupSwatch = swatch.firstChildElement("swatch");
while(!groupSwatch.isNull()) {
QString id = groupSwatch.attribute("material");
if (id.isEmpty() || id.isNull()) {
warnPigment << "Invalid swatch definition (no material id) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")";
return false;
}
if (materialsBook.contains(id)) {
groups[currentGroupName].addEntry(materialsBook.value(id));
}
else {
warnPigment << "Invalid swatch definition (material not found) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")";
return false;
}
groupSwatch = groupSwatch.nextSiblingElement("swatch");
}
}
}
// End palette
}
buf.close();
return true;
}
bool KoColorSet::Private::loadXml() {
bool res = false;
QXmlStreamReader *xml = new QXmlStreamReader(data);
if (xml->readNextStartElement()) {
QStringRef paletteId = xml->name();
if (QStringRef::compare(paletteId, "SCRIBUSCOLORS", Qt::CaseInsensitive) == 0) { // Scribus
dbgPigment << "XML palette: " << colorSet->filename() << ", Scribus format";
res = loadScribusXmlPalette(colorSet, xml);
}
else {
// Unknown XML format
xml->raiseError("Unknown XML palette format. Expected SCRIBUSCOLORS, found " + paletteId);
}
}
// If there is any error (it should be returned through the stream)
if (xml->hasError() || !res) {
warnPigment << "Illegal XML palette:" << colorSet->filename();
warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString();
return false;
}
else {
dbgPigment << "XML palette parsed successfully:" << colorSet->filename();
return true;
}
}
bool KoColorSet::Private::saveKpl(QIODevice *dev) const
{
QScopedPointer store(KoStore::createStore(dev, KoStore::Write, "krita/x-colorset", KoStore::Zip));
if (!store || store->bad()) return false;
QSet colorSpaces;
{
QDomDocument doc;
QDomElement root = doc.createElement(KPL_PALETTE_TAG);
root.setAttribute(KPL_VERSION_ATTR, "1.0");
root.setAttribute(KPL_PALETTE_NAME_ATTR, colorSet->name());
root.setAttribute(KPL_PALETTE_COMMENT_ATTR, comment);
root.setAttribute(KPL_PALETTE_READONLY_ATTR,
(colorSet->isEditable() || !colorSet->isGlobal()) ? "false" : "true");
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/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp
index e3a5076389..affa7e6893 100644
--- a/libs/ui/KisImportExportManager.cpp
+++ b/libs/ui/KisImportExportManager.cpp
@@ -1,681 +1,680 @@
/*
* 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 "KisImportExportManager.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 "kis_config.h"
#include "KisImportExportFilter.h"
#include "KisDocument.h"
#include
#include
#include "kis_painter.h"
#include "kis_guides_config.h"
#include "kis_grid_config.h"
#include "kis_popup_button.h"
#include
#include "kis_async_action_feedback.h"
#include "KisReferenceImagesLayer.h"
// static cache for import and export mimetypes
QStringList KisImportExportManager::m_importMimeTypes;
QStringList KisImportExportManager::m_exportMimeTypes;
class Q_DECL_HIDDEN KisImportExportManager::Private
{
public:
KoUpdaterPtr updater;
QString cachedExportFilterMimeType;
QSharedPointer cachedExportFilter;
};
struct KisImportExportManager::ConversionResult {
ConversionResult()
{
}
ConversionResult(const QFuture &futureStatus)
: m_isAsync(true),
m_futureStatus(futureStatus)
{
}
ConversionResult(KisImportExportFilter::ConversionStatus status)
: m_isAsync(false),
m_status(status)
{
}
bool isAsync() const {
return m_isAsync;
}
QFuture futureStatus() const {
// if the result is not async, then it means some failure happened,
// just return a cancelled future
KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || m_status != KisImportExportFilter::OK);
return m_futureStatus;
}
KisImportExportFilter::ConversionStatus status() const {
return m_status;
}
void setStatus(KisImportExportFilter::ConversionStatus value) {
m_status = value;
}
private:
bool m_isAsync = false;
QFuture m_futureStatus;
KisImportExportFilter::ConversionStatus m_status = KisImportExportFilter::UsageError;
};
KisImportExportManager::KisImportExportManager(KisDocument* document)
: m_document(document)
, d(new Private)
{
}
KisImportExportManager::~KisImportExportManager()
{
delete d;
}
KisImportExportFilter::ConversionStatus KisImportExportManager::importDocument(const QString& location, const QString& mimeType)
{
ConversionResult result = convert(Import, location, location, mimeType, false, 0, false);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError);
return result.status();
}
KisImportExportFilter::ConversionStatus KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError);
return result.status();
}
QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, KisImportExportFilter::ConversionStatus &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync() ||
result.status() != KisImportExportFilter::OK, QFuture());
status = result.status();
return result.futureStatus();
}
// The static method to figure out to which parts of the
// graph this mimetype has a connection to.
QStringList KisImportExportManager::supportedMimeTypes(Direction direction)
{
// Find the right mimetype by the extension
QSet mimeTypes;
// mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster";
if (direction == KisImportExportManager::Import) {
if (m_importMimeTypes.isEmpty()) {
QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_importMimeTypes = mimeTypes.toList();
}
return m_importMimeTypes;
}
else if (direction == KisImportExportManager::Export) {
if (m_exportMimeTypes.isEmpty()) {
QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_exportMimeTypes = mimeTypes.toList();
}
return m_exportMimeTypes;
}
return QStringList();
}
KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction)
{
int weight = -1;
KisImportExportFilter *filter = 0;
QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import";
if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) {
KLibFactory *factory = qobject_cast(loader->instance());
if (!factory) {
warnUI << loader->errorString();
continue;
}
QObject* obj = factory->create(0);
if (!obj || !obj->inherits("KisImportExportFilter")) {
delete obj;
continue;
}
KisImportExportFilter *f = qobject_cast(obj);
if (!f) {
delete obj;
continue;
}
int w = json.value("X-KDE-Weight").toInt();
if (w > weight) {
delete filter;
filter = f;
f->setObjectName(loader->fileName());
weight = w;
}
}
}
qDeleteAll(list);
if (filter) {
filter->setMimeType(mimetype);
}
return filter;
}
bool KisImportExportManager::batchMode(void) const
{
return m_document->fileBatchMode();
}
void KisImportExportManager::setUpdater(KoUpdaterPtr updater)
{
d->updater = updater;
}
QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent)
{
KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio");
if (!defaultDir.isEmpty()) {
dialog.setDefaultDir(defaultDir);
}
QStringList mimeTypes;
mimeTypes << "audio/mpeg";
mimeTypes << "audio/ogg";
mimeTypes << "audio/vorbis";
mimeTypes << "audio/vnd.wave";
mimeTypes << "audio/flac";
dialog.setMimeTypeFilters(mimeTypes);
dialog.setCaption(i18nc("@titile:window", "Open Audio"));
return dialog.filename();
}
KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync)
{
// export configuration is supported for export only
KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration));
QString typeName = mimeType;
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(location, direction == KisImportExportManager::Export ? false : true);
}
QSharedPointer filter;
/**
* Fetching a filter from the registry is a really expensive operation,
* because it blocks all the threads. Cache the filter if possible.
*/
if (direction == KisImportExportManager::Export &&
d->cachedExportFilter &&
d->cachedExportFilterMimeType == typeName) {
filter = d->cachedExportFilter;
} else {
filter = toQShared(filterForMimeType(typeName, direction));
if (direction == Export) {
d->cachedExportFilter = filter;
d->cachedExportFilterMimeType = typeName;
}
}
if (!filter) {
return KisImportExportFilter::FilterCreationError;
}
filter->setFilename(location);
filter->setRealFilename(realLocation);
filter->setBatchMode(batchMode());
filter->setMimeType(typeName);
if (!d->updater.isNull()) {
// WARNING: The updater is not guaranteed to be persistent! If you ever want
// to add progress reporting to "Save also as .kra", make sure you create
// a separate KoProgressUpdater for that!
// WARNING2: the failsafe completion of the updater happens in the destructor
// the filter.
filter->setUpdater(d->updater);
}
QByteArray from, to;
if (direction == Export) {
from = m_document->nativeFormatMimeType();
to = typeName.toLatin1();
}
else {
from = typeName.toLatin1();
to = m_document->nativeFormatMimeType();
}
KIS_ASSERT_RECOVER_RETURN_VALUE(
direction == Import || direction == Export,
KisImportExportFilter::BadConversionGraph);
ConversionResult result = KisImportExportFilter::OK;
if (direction == Import) {
// async importing is not yet supported!
KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync);
if (0 && !batchMode()) {
KisAsyncActionFeedback f(i18n("Opening document..."), 0);
result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter));
} else {
result = doImport(location, filter);
}
}
else /* if (direction == Export) */ {
if (!exportConfiguration) {
exportConfiguration = filter->lastSavedConfiguration(from, to);
}
if (exportConfiguration) {
fillStaticExportConfigurationProperties(exportConfiguration);
}
bool alsoAsKra = false;
bool askUser = askUserAboutExportConfiguration(filter, exportConfiguration,
from, to,
batchMode(), showWarnings,
&alsoAsKra);
if (!batchMode() && !askUser) {
return KisImportExportFilter::UserCancelled;
}
if (isAsync) {
result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra));
// we should explicitly report that the exporting has been initiated
result.setStatus(KisImportExportFilter::OK);
} else if (!batchMode()) {
KisAsyncActionFeedback f(i18n("Saving document..."), 0);
result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra));
} else {
result = doExport(location, filter, exportConfiguration, alsoAsKra);
}
if (exportConfiguration && !batchMode() && showWarnings) {
KisConfig(false).setExportConfiguration(typeName, exportConfiguration);
}
}
return result;
}
void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image)
{
KisPaintDeviceSP dev = image->projection();
const KoColorSpace* cs = dev->colorSpace();
const bool isThereAlpha =
KisPainter::checkDeviceHasTransparency(image->projection());
exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha);
exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id());
exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id());
const bool sRGB =
(cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) &&
!cs->profile()->name().contains(QLatin1String("g10")));
exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB);
}
void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration)
{
return fillStaticExportConfigurationProperties(exportConfiguration, m_document->image());
}
bool KisImportExportManager::askUserAboutExportConfiguration(
QSharedPointer filter,
KisPropertiesConfigurationSP exportConfiguration,
const QByteArray &from,
const QByteArray &to,
const bool batchMode,
const bool showWarnings,
bool *alsoAsKra)
{
// prevents the animation renderer from running this code
const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to);
QStringList warnings;
QStringList errors;
{
KisPreExportChecker checker;
checker.check(m_document->image(), filter->exportChecks());
warnings = checker.warnings();
errors = checker.errors();
}
KisConfigWidget *wdg = 0;
if (QThread::currentThread() == qApp->thread()) {
wdg = filter->createConfigurationWidget(0, from, to);
}
// Extra checks that cannot be done by the checker, because the checker only has access to the image.
if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains assistants . The assistants will not be saved."));
}
if (m_document->referenceImagesLayer() && m_document->referenceImagesLayer()->shapeCount() > 0 && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains reference images . The reference images will not be saved."));
}
if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains guides . The guides will not be saved."));
}
if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains a custom grid configuration . The configuration will not be saved."));
}
if (!batchMode && !errors.isEmpty()) {
QString error = ""
+ i18n("Error: cannot save this image as a %1.", mimeUserDescription)
+ " Reasons:
"
+ "
";
Q_FOREACH(const QString &w, errors) {
error += "\n" + w + " ";
}
error += " ";
QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error);
return false;
}
if (!batchMode && (wdg || !warnings.isEmpty())) {
KoDialog dlg;
dlg.setButtons(KoDialog::Ok | KoDialog::Cancel);
dlg.setWindowTitle(mimeUserDescription);
QWidget *page = new QWidget(&dlg);
QVBoxLayout *layout = new QVBoxLayout(page);
if (showWarnings && !warnings.isEmpty()) {
QHBoxLayout *hLayout = new QHBoxLayout();
QLabel *labelWarning = new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32));
hLayout->addWidget(labelWarning);
KisPopupButton *bn = new KisPopupButton(0);
bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as %1 will lose information from your image. ", mimeUserDescription));
hLayout->addWidget(bn);
layout->addLayout(hLayout);
QTextBrowser *browser = new QTextBrowser();
browser->setMinimumWidth(bn->width());
bn->setPopupWidget(browser);
QString warning = ""
+ i18n("You will lose information when saving this image as a %1.", mimeUserDescription);
if (warnings.size() == 1) {
warning += " Reason:
";
}
else {
warning += " Reasons:
";
}
warning += "
";
Q_FOREACH(const QString &w, warnings) {
warning += "\n" + w + " ";
}
warning += " ";
browser->setHtml(warning);
}
if (wdg) {
QGroupBox *box = new QGroupBox(i18n("Options"));
QVBoxLayout *boxLayout = new QVBoxLayout(box);
wdg->setConfiguration(exportConfiguration);
boxLayout->addWidget(wdg);
layout->addWidget(box);
}
QCheckBox *chkAlsoAsKra = 0;
if (showWarnings && !warnings.isEmpty()) {
chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file."));
chkAlsoAsKra->setChecked(KisConfig(true).readEntry("AlsoSaveAsKra", false));
layout->addWidget(chkAlsoAsKra);
}
dlg.setMainWidget(page);
dlg.resize(dlg.minimumSize());
if (showWarnings || wdg) {
if (!dlg.exec()) {
return false;
}
}
*alsoAsKra = false;
if (chkAlsoAsKra) {
KisConfig(false).writeEntry("AlsoSaveAsKra", chkAlsoAsKra->isChecked());
*alsoAsKra = chkAlsoAsKra->isChecked();
}
if (wdg) {
*exportConfiguration = *wdg->configuration();
}
}
return true;
}
KisImportExportFilter::ConversionStatus KisImportExportManager::doImport(const QString &location, QSharedPointer filter)
{
QFile file(location);
if (!file.exists()) {
return KisImportExportFilter::FileNotFound;
}
if (filter->supportsIO() && !file.open(QFile::ReadOnly)) {
return KisImportExportFilter::FileNotFound;
}
KisImportExportFilter::ConversionStatus status =
filter->convert(m_document, &file, KisPropertiesConfigurationSP());
if (file.isOpen()) {
file.close();
}
return status;
}
KisImportExportFilter::ConversionStatus KisImportExportManager::doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra)
{
KisImportExportFilter::ConversionStatus status =
doExportImpl(location, filter, exportConfiguration);
if (alsoAsKra && status == KisImportExportFilter::OK) {
QString kraLocation = location + ".kra";
QByteArray mime = m_document->nativeFormatMimeType();
QSharedPointer filter(
filterForMimeType(QString::fromLatin1(mime), Export));
KIS_SAFE_ASSERT_RECOVER_NOOP(filter);
if (filter) {
filter->setFilename(kraLocation);
KisPropertiesConfigurationSP kraExportConfiguration =
filter->lastSavedConfiguration(mime, mime);
status = doExportImpl(kraLocation, filter, kraExportConfiguration);
} else {
status = KisImportExportFilter::FilterCreationError;
}
}
return status;
}
// Temporary workaround until QTBUG-57299 is fixed.
#ifndef Q_OS_WIN
#define USE_QSAVEFILE
#endif
KisImportExportFilter::ConversionStatus KisImportExportManager::doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration)
{
#ifdef USE_QSAVEFILE
QSaveFile file(location);
file.setDirectWriteFallback(true);
if (filter->supportsIO() && !file.open(QFile::WriteOnly)) {
#else
QFileInfo fi(location);
QTemporaryFile file(fi.absolutePath() + ".XXXXXX.kra");
if (filter->supportsIO() && !file.open()) {
#endif
QString error = file.errorString();
if (error.isEmpty()) {
error = i18n("Could not open %1 for writing.", location);
}
m_document->setErrorMessage(error);
#ifdef USE_QSAVEFILE
file.cancelWriting();
#endif
return KisImportExportFilter::CreationError;
}
KisImportExportFilter::ConversionStatus status = filter->convert(m_document, &file, exportConfiguration);
if (filter->supportsIO()) {
if (status != KisImportExportFilter::OK) {
#ifdef USE_QSAVEFILE
file.cancelWriting();
#endif
} else {
#ifdef USE_QSAVEFILE
if (!file.commit()) {
QString error = file.errorString();
if (error.isEmpty()) {
error = i18n("Could not write to %1.", location);
}
if (m_document->errorMessage().isEmpty()) {
m_document->setErrorMessage(error);
}
status = KisImportExportFilter::CreationError;
}
#else
file.flush();
file.close();
QFile target(location);
if (target.exists()) {
// There should already be a .kra~ backup
target.remove();
}
if (!file.copy(location)) {
file.setAutoRemove(false);
m_document->setErrorMessage(i18n("Could not copy %1 to its final location %2", file.fileName(), location));
return KisImportExportFilter::CreationError;
}
#endif
}
}
return status;
}
#include
diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp
index 56ae74c5ca..26c83b1ef8 100644
--- a/libs/ui/KisMainWindow.cpp
+++ b/libs/ui/KisMainWindow.cpp
@@ -1,2668 +1,2667 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis
Copyright (C) 2000-2006 David Faure
Copyright (C) 2007, 2009 Thomas zander
Copyright (C) 2010 Benjamin Port
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisMainWindow.h"
#include
// qt includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_selection_manager.h"
#include "kis_icon_utils.h"
#ifdef HAVE_KIO
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoDockFactoryBase.h"
#include "KoDocumentInfoDlg.h"
#include "KoDocumentInfo.h"
#include "KoFileDialog.h"
#include
#include
#include
#include
#include
#include "KoToolDocker.h"
#include "KoToolBoxDocker_p.h"
#include
#include
#include
#include
#include
#include
#include
#include "dialogs/kis_about_application.h"
#include "dialogs/kis_delayed_save_dialog.h"
#include "dialogs/kis_dlg_preferences.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "KisApplication.h"
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
#include "kis_canvas_resource_provider.h"
#include "kis_clipboard.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_custom_image_widget.h"
#include
#include "kis_group_layer.h"
-#include "kis_icon_utils.h"
#include "kis_image_from_clipboard_widget.h"
#include "kis_image.h"
#include
#include "KisImportExportManager.h"
#include "kis_mainwindow_observer.h"
#include "kis_memory_statistics_server.h"
#include "kis_node.h"
#include "KisOpenPane.h"
#include "kis_paintop_box.h"
#include "KisPart.h"
#include "KisPrintJob.h"
#include "KisResourceServerProvider.h"
#include "kis_signal_compressor_with_param.h"
#include "kis_statusbar.h"
#include "KisView.h"
#include "KisViewManager.h"
#include "thememanager.h"
#include "kis_animation_importer.h"
#include "dialogs/kis_dlg_import_image_sequence.h"
#include
#include "KisWindowLayoutManager.h"
#include
#include "KisWelcomePageWidget.h"
#include
#ifdef Q_OS_WIN
#include
#endif
class ToolDockerFactory : public KoDockFactoryBase
{
public:
ToolDockerFactory() : KoDockFactoryBase() { }
QString id() const override {
return "sharedtooldocker";
}
QDockWidget* createDockWidget() override {
KoToolDocker* dockWidget = new KoToolDocker();
return dockWidget;
}
DockPosition defaultDockPosition() const override {
return DockRight;
}
};
class Q_DECL_HIDDEN KisMainWindow::Private
{
public:
Private(KisMainWindow *parent, QUuid id)
: q(parent)
, id(id)
, dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent))
, windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent))
, documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent))
, workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent))
, welcomePage(new KisWelcomePageWidget(parent))
, widgetStack(new QStackedWidget(parent))
, mdiArea(new QMdiArea(parent))
, windowMapper(new QSignalMapper(parent))
, documentMapper(new QSignalMapper(parent))
{
if (id.isNull()) this->id = QUuid::createUuid();
widgetStack->addWidget(welcomePage);
widgetStack->addWidget(mdiArea);
mdiArea->setTabsMovable(true);
mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder);
}
~Private() {
qDeleteAll(toolbarList);
}
KisMainWindow *q {0};
QUuid id;
KisViewManager *viewManager {0};
QPointer activeView;
QList toolbarList;
bool firstTime {true};
bool windowSizeDirty {false};
bool readOnly {false};
KisAction *showDocumentInfo {0};
KisAction *saveAction {0};
KisAction *saveActionAs {0};
// KisAction *printAction;
// KisAction *printActionPreview;
// KisAction *exportPdf {0};
KisAction *importAnimation {0};
KisAction *closeAll {0};
// KisAction *reloadFile;
KisAction *importFile {0};
KisAction *exportFile {0};
KisAction *undo {0};
KisAction *redo {0};
KisAction *newWindow {0};
KisAction *close {0};
KisAction *mdiCascade {0};
KisAction *mdiTile {0};
KisAction *mdiNextWindow {0};
KisAction *mdiPreviousWindow {0};
KisAction *toggleDockers {0};
KisAction *toggleDockerTitleBars {0};
KisAction *fullScreenMode {0};
KisAction *showSessionManager {0};
KisAction *expandingSpacers[2];
KActionMenu *dockWidgetMenu;
KActionMenu *windowMenu;
KActionMenu *documentMenu;
KActionMenu *workspaceMenu;
KHelpMenu *helpMenu {0};
KRecentFilesAction *recentFiles {0};
KoResourceModel *workspacemodel {0};
QScopedPointer undoActionsUpdateManager;
QString lastExportLocation;
QMap dockWidgetsMap;
QByteArray dockerStateBeforeHiding;
KoToolDocker *toolOptionsDocker {0};
QCloseEvent *deferredClosingEvent {0};
Digikam::ThemeManager *themeManager {0};
KisWelcomePageWidget *welcomePage {0};
QStackedWidget *widgetStack {0};
QMdiArea *mdiArea;
QMdiSubWindow *activeSubWindow {0};
QSignalMapper *windowMapper;
QSignalMapper *documentMapper;
QByteArray lastExportedFormat;
QScopedPointer > tabSwitchCompressor;
QMutex savingEntryMutex;
KConfigGroup windowStateConfig;
QUuid workspaceBorrowedBy;
KisActionManager * actionManager() {
return viewManager->actionManager();
}
QTabBar* findTabBarHACK() {
QObjectList objects = mdiArea->children();
Q_FOREACH (QObject *object, objects) {
QTabBar *bar = qobject_cast(object);
if (bar) {
return bar;
}
}
return 0;
}
};
KisMainWindow::KisMainWindow(QUuid uuid)
: KXmlGuiWindow()
, d(new Private(this, uuid))
{
auto rserver = KisResourceServerProvider::instance()->workspaceServer();
QSharedPointer adapter(new KoResourceServerAdapter(rserver));
d->workspacemodel = new KoResourceModel(adapter, this);
connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); });
d->viewManager = new KisViewManager(this, actionCollection());
KConfigGroup group( KSharedConfig::openConfig(), "theme");
d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this);
d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow");
setAcceptDrops(true);
setStandardToolBarMenuEnabled(true);
setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North);
setDockNestingEnabled(true);
qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events
#ifdef Q_OS_OSX
setUnifiedTitleAndToolBarOnMac(true);
#endif
connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts()));
connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons()));
connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu()));
connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu()));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged()));
actionCollection()->addAssociatedWidget(this);
KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false);
// Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created.
KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true);
KoToolBoxFactory toolBoxFactory;
QDockWidget *toolbox = createDockWidget(&toolBoxFactory);
toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable);
KisConfig cfg(true);
if (cfg.toolOptionsInDocker()) {
ToolDockerFactory toolDockerFactory;
d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory));
d->toolOptionsDocker->toggleViewAction()->setEnabled(true);
}
QMap dockwidgetActions;
dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction();
Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) {
KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker);
QDockWidget *dw = createDockWidget(factory);
dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction();
}
if (d->toolOptionsDocker) {
dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction();
}
connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList >)));
Q_FOREACH (QString title, dockwidgetActions.keys()) {
d->dockWidgetMenu->addAction(dockwidgetActions[title]);
}
Q_FOREACH (QDockWidget *wdg, dockWidgets()) {
if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) {
wdg->setVisible(true);
}
}
Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) {
observer->setObservedCanvas(0);
KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer);
if (mainwindowObserver) {
mainwindowObserver->setViewManager(d->viewManager);
}
}
d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
d->mdiArea->setTabPosition(QTabWidget::North);
d->mdiArea->setTabsClosable(true);
// Tab close button override
// Windows just has a black X, and Ubuntu has a dark x that is hard to read
// just switch this icon out for all OSs so it is easier to see
d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }");
setCentralWidget(d->widgetStack);
d->widgetStack->setCurrentIndex(0);
connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated()));
connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*)));
createActions();
// the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist
d->welcomePage->setMainWindow(this);
setAutoSaveSettings(d->windowStateConfig, false);
subWindowActivated();
updateWindowMenu();
if (isHelpMenuEnabled() && !d->helpMenu) {
// workaround for KHelpMenu (or rather KAboutData::applicationData()) internally
// not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard
// not having the app version preset
// fixed hopefully in KF5 5.22.0, patch pending
QGuiApplication *app = qApp;
KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion());
aboutData.setOrganizationDomain(app->organizationDomain().toUtf8());
d->helpMenu = new KHelpMenu(this, aboutData, false);
// workaround-less version:
// d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false);
// The difference between using KActionCollection->addAction() is that
// these actions do not get tied to the MainWindow. What does this all do?
KActionCollection *actions = d->viewManager->actionCollection();
QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents);
QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis);
QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug);
QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage);
QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp);
QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE);
if (helpContentsAction) {
actions->addAction(helpContentsAction->objectName(), helpContentsAction);
}
if (whatsThisAction) {
actions->addAction(whatsThisAction->objectName(), whatsThisAction);
}
if (reportBugAction) {
actions->addAction(reportBugAction->objectName(), reportBugAction);
}
if (switchLanguageAction) {
actions->addAction(switchLanguageAction->objectName(), switchLanguageAction);
}
if (aboutAppAction) {
actions->addAction(aboutAppAction->objectName(), aboutAppAction);
}
if (aboutKdeAction) {
actions->addAction(aboutKdeAction->objectName(), aboutKdeAction);
}
connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication()));
}
// KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves
QAction *helpAction = actionCollection()->action("help_contents");
helpAction->disconnect();
connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual()));
#if 0
//check for colliding shortcuts
QSet existingShortcuts;
Q_FOREACH (QAction* action, actionCollection()->actions()) {
if(action->shortcut() == QKeySequence(0)) {
continue;
}
dbgKrita << "shortcut " << action->text() << " " << action->shortcut();
Q_ASSERT(!existingShortcuts.contains(action->shortcut()));
existingShortcuts.insert(action->shortcut());
}
#endif
configChanged();
// If we have customized the toolbars, load that first
setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui"));
setXMLFile(":/kxmlgui5/krita4.xmlgui");
guiFactory()->addClient(this);
// Create and plug toolbar list for Settings menu
QList toolbarList;
Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) {
KToolBar * toolBar = ::qobject_cast(it);
if (toolBar) {
if (toolBar->objectName() == "BrushesAndStuff") {
toolBar->setEnabled(false);
}
KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this);
actionCollection()->addAction(toolBar->objectName().toUtf8(), act);
act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle())));
connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool)));
act->setChecked(!toolBar->isHidden());
toolbarList.append(act);
} else {
warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!";
}
}
plugActionList("toolbarlist", toolbarList);
d->toolbarList = toolbarList;
applyToolBarLayout();
d->viewManager->updateGUI();
d->viewManager->updateIcons();
#ifdef Q_OS_WIN
auto w = qApp->activeWindow();
if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true);
#endif
QTimer::singleShot(1000, this, SLOT(checkSanity()));
{
using namespace std::placeholders; // For _1 placeholder
std::function callback(
std::bind(&KisMainWindow::switchTab, this, _1));
d->tabSwitchCompressor.reset(
new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE));
}
}
KisMainWindow::~KisMainWindow()
{
// Q_FOREACH (QAction *ac, actionCollection()->actions()) {
// QAction *action = qobject_cast(ac);
// if (action) {
// dbgKrita << "objectName()
// << "icon=" << action->icon().name()
// << "text=" << action->text().replace("&", "&")
// << "whatsThis=" << action->whatsThis()
// << "toolTip=" << action->toolTip().replace("", "").replace("", "")
// << "iconText=" << action->iconText().replace("&", "&")
// << "shortcut=" << action->shortcut(QAction::ActiveShortcut).toString()
// << "defaultShortcut=" << action->shortcut(QAction::DefaultShortcut).toString()
// << "isCheckable=" << QString((action->isChecked() ? "true" : "false"))
// << "statusTip=" << action->statusTip()
// << "/>" ;
// }
// else {
// dbgKrita << "Got a QAction:" << ac->objectName();
// }
// }
// The doc and view might still exist (this is the case when closing the window)
KisPart::instance()->removeMainWindow(this);
delete d->viewManager;
delete d;
}
QUuid KisMainWindow::id() const {
return d->id;
}
void KisMainWindow::addView(KisView *view)
{
if (d->activeView == view) return;
if (d->activeView) {
d->activeView->disconnect(this);
}
// register the newly created view in the input manager
viewManager()->inputManager()->addTrackedCanvas(view->canvasBase());
showView(view);
updateCaption();
emit restoringDone();
if (d->activeView) {
connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified()));
connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption()));
}
}
void KisMainWindow::notifyChildViewDestroyed(KisView *view)
{
viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase());
if (view->canvasBase() == viewManager()->canvasBase()) {
viewManager()->setCurrentView(0);
}
}
void KisMainWindow::showView(KisView *imageView)
{
if (imageView && activeView() != imageView) {
// XXX: find a better way to initialize this!
imageView->setViewManager(d->viewManager);
imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager());
imageView->slotLoadingFinished();
QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView);
imageView->setSubWindow(subwin);
subwin->setAttribute(Qt::WA_DeleteOnClose, true);
connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu()));
KisConfig cfg(true);
subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL()));
subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL()));
subwin->setWindowIcon(qApp->windowIcon());
/**
* Hack alert!
*
* Here we explicitly request KoToolManager to emit all the tool
* activation signals, to reinitialize the tool options docker.
*
* That is needed due to a design flaw we have in the
* initialization procedure. The tool in the KoToolManager is
* initialized in KisView::setViewManager() calls, which
* happens early enough. During this call the tool manager
* requests KoCanvasControllerWidget to emit the signal to
* update the widgets in the tool docker. *But* at that moment
* of time the view is not yet connected to the main window,
* because it happens in KisViewManager::setCurrentView a bit
* later. This fact makes the widgets updating signals be lost
* and never reach the tool docker.
*
* So here we just explicitly call the tool activation stub.
*/
KoToolManager::instance()->initializeCurrentToolForCanvas();
if (d->mdiArea->subWindowList().size() == 1) {
imageView->showMaximized();
}
else {
imageView->show();
}
// No, no, no: do not try to call this _before_ the show() has
// been called on the view; only when that has happened is the
// opengl context active, and very bad things happen if we tell
// the dockers to update themselves with a view if the opengl
// context is not active.
setActiveView(imageView);
updateWindowMenu();
updateCaption();
}
}
void KisMainWindow::slotPreferences()
{
if (KisDlgPreferences::editPreferences()) {
KisConfigNotifier::instance()->notifyConfigChanged();
KisConfigNotifier::instance()->notifyPixelGridModeChanged();
KisImageConfigNotifier::instance()->notifyConfigChanged();
// XXX: should this be changed for the views in other windows as well?
Q_FOREACH (QPointer koview, KisPart::instance()->views()) {
KisViewManager *view = qobject_cast(koview);
if (view) {
// Update the settings for all nodes -- they don't query
// KisConfig directly because they need the settings during
// compositing, and they don't connect to the config notifier
// because nodes are not QObjects (because only one base class
// can be a QObject).
KisNode* node = dynamic_cast(view->image()->rootLayer().data());
node->updateSettings();
}
}
d->viewManager->showHideScrollbars();
}
}
void KisMainWindow::slotThemeChanged()
{
// save theme changes instantly
KConfigGroup group( KSharedConfig::openConfig(), "theme");
group.writeEntry("Theme", d->themeManager->currentThemeName());
// reload action icons!
Q_FOREACH (QAction *action, actionCollection()->actions()) {
KisIconUtils::updateIcon(action);
}
emit themeChanged();
}
void KisMainWindow::updateReloadFileAction(KisDocument *doc)
{
Q_UNUSED(doc);
// d->reloadFile->setEnabled(doc && !doc->url().isEmpty());
}
void KisMainWindow::setReadWrite(bool readwrite)
{
d->saveAction->setEnabled(readwrite);
d->importFile->setEnabled(readwrite);
d->readOnly = !readwrite;
updateCaption();
}
void KisMainWindow::addRecentURL(const QUrl &url)
{
// Add entry to recent documents list
// (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.)
if (!url.isEmpty()) {
bool ok = true;
if (url.isLocalFile()) {
QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile();
const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp");
for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) {
if (path.contains(*it)) {
ok = false; // it's in the tmp resource
}
}
#ifdef HAVE_KIO
if (ok) {
KRecentDocument::add(QUrl::fromLocalFile(path));
}
#endif
}
#ifdef HAVE_KIO
else {
KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash));
}
#endif
if (ok) {
d->recentFiles->addUrl(url);
}
saveRecentFiles();
}
}
void KisMainWindow::saveRecentFiles()
{
// Save list of recent files
KSharedConfigPtr config = KSharedConfig::openConfig();
d->recentFiles->saveEntries(config->group("RecentFiles"));
config->sync();
// Tell all windows to reload their list, after saving
// Doesn't work multi-process, but it's a start
Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) {
if (mw != this) {
mw->reloadRecentFileList();
}
}
}
QList KisMainWindow::recentFilesUrls()
{
return d->recentFiles->urls();
}
void KisMainWindow::clearRecentFiles()
{
d->recentFiles->clear();
}
void KisMainWindow::reloadRecentFileList()
{
d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles"));
}
void KisMainWindow::updateCaption()
{
if (!d->mdiArea->activeSubWindow()) {
updateCaption(QString(), false);
}
else if (d->activeView && d->activeView->document() && d->activeView->image()){
KisDocument *doc = d->activeView->document();
QString caption(doc->caption());
if (d->readOnly) {
caption += " [" + i18n("Write Protected") + "] ";
}
if (doc->isRecovered()) {
caption += " [" + i18n("Recovered") + "] ";
}
// new documents aren't saved yet, so we don't need to say it is modified
// new files don't have a URL, so we are using that for the check
if (!doc->url().isEmpty()) {
if ( doc->isModified()) {
caption += " [" + i18n("Modified") + "] ";
}
}
// show the file size for the document
KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0);
if (m_fileSizeStats.imageSize) {
caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")");
}
d->activeView->setWindowTitle(caption);
d->activeView->setWindowModified(doc->isModified());
updateCaption(caption, doc->isModified());
if (!doc->url().fileName().isEmpty())
d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName()));
else
d->saveAction->setToolTip(i18n("Save"));
}
}
void KisMainWindow::updateCaption(const QString & caption, bool mod)
{
dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")";
#ifdef KRITA_ALPHA
setCaption(QString("ALPHA %1: %2").arg(KRITA_ALPHA).arg(caption), mod);
return;
#endif
#ifdef KRITA_BETA
setCaption(QString("BETA %1: %2").arg(KRITA_BETA).arg(caption), mod);
return;
#endif
#ifdef KRITA_RC
setCaption(QString("RELEASE CANDIDATE %1: %2").arg(KRITA_RC).arg(caption), mod);
return;
#endif
setCaption(caption, mod);
}
KisView *KisMainWindow::activeView() const
{
if (d->activeView) {
return d->activeView;
}
return 0;
}
bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags)
{
if (!QFile(url.toLocalFile()).exists()) {
if (!(flags & BatchMode)) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url()));
}
d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list
saveRecentFiles();
return false;
}
return openDocumentInternal(url, flags);
}
bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags)
{
if (!url.isLocalFile()) {
qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url;
return false;
}
KisDocument *newdoc = KisPart::instance()->createDocument();
if (flags & BatchMode) {
newdoc->setFileBatchMode(true);
}
d->firstTime = true;
connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString)));
KisDocument::OpenFlags openFlags = KisDocument::None;
if (flags & RecoveryFile) {
openFlags |= KisDocument::RecoveryFile;
}
bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url);
if (!openRet) {
delete newdoc;
return false;
}
KisPart::instance()->addDocument(newdoc);
updateReloadFileAction(newdoc);
if (!QFileInfo(url.toLocalFile()).isWritable()) {
setReadWrite(false);
}
return true;
}
void KisMainWindow::showDocument(KisDocument *document) {
Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) {
KisView *view = qobject_cast(subwindow->widget());
KIS_SAFE_ASSERT_RECOVER_NOOP(view);
if (view) {
if (view->document() == document) {
setActiveSubWindow(subwindow);
return;
}
}
}
addViewAndNotifyLoadingCompleted(document);
}
KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document)
{
showWelcomeScreen(false); // see workaround in function header
KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this);
addView(view);
emit guiLoadingFinished();
return view;
}
QStringList KisMainWindow::showOpenFileDialog(bool isImporting)
{
KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument");
dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import));
dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images"));
return dialog.filenames();
}
// Separate from openDocument to handle async loading (remote URLs)
void KisMainWindow::slotLoadCompleted()
{
KisDocument *newdoc = qobject_cast(sender());
if (newdoc && newdoc->image()) {
addViewAndNotifyLoadingCompleted(newdoc);
disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString)));
emit loadCompleted();
}
}
void KisMainWindow::slotLoadCanceled(const QString & errMsg)
{
dbgUI << "KisMainWindow::slotLoadCanceled";
if (!errMsg.isEmpty()) // empty when canceled by user
QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg);
// ... can't delete the document, it's the one who emitted the signal...
KisDocument* doc = qobject_cast(sender());
Q_ASSERT(doc);
disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString)));
}
void KisMainWindow::slotSaveCanceled(const QString &errMsg)
{
dbgUI << "KisMainWindow::slotSaveCanceled";
if (!errMsg.isEmpty()) // empty when canceled by user
QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg);
slotSaveCompleted();
}
void KisMainWindow::slotSaveCompleted()
{
dbgUI << "KisMainWindow::slotSaveCompleted";
KisDocument* doc = qobject_cast(sender());
Q_ASSERT(doc);
disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString)));
if (d->deferredClosingEvent) {
KXmlGuiWindow::closeEvent(d->deferredClosingEvent);
}
}
bool KisMainWindow::hackIsSaving() const
{
StdLockableWrapper wrapper(&d->savingEntryMutex);
std::unique_lock> l(wrapper, std::try_to_lock);
return !l.owns_lock();
}
bool KisMainWindow::installBundle(const QString &fileName) const
{
QFileInfo from(fileName);
QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName());
if (to.exists()) {
QFile::remove(to.canonicalFilePath());
}
return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName());
}
bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting)
{
if (!document) {
return true;
}
/**
* Make sure that we cannot enter this method twice!
*
* The lower level functions may call processEvents() so
* double-entry is quite possible to achieve. Here we try to lock
* the mutex, and if it is failed, just cancel saving.
*/
StdLockableWrapper wrapper(&d->savingEntryMutex);
std::unique_lock> l(wrapper, std::try_to_lock);
if (!l.owns_lock()) return false;
// no busy wait for saving because it is dangerous!
KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this);
dlg.blockIfImageIsBusy();
if (dlg.result() == KisDelayedSaveDialog::Rejected) {
return false;
}
else if (dlg.result() == KisDelayedSaveDialog::Ignored) {
QMessageBox::critical(0,
i18nc("@title:window", "Krita"),
i18n("You are saving a file while the image is "
"still rendering. The saved file may be "
"incomplete or corrupted.\n\n"
"Please select a location where the original "
"file will not be overridden!"));
saveas = true;
}
if (document->isRecovered()) {
saveas = true;
}
if (document->url().isEmpty()) {
saveas = true;
}
connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString)));
QByteArray nativeFormat = document->nativeFormatMimeType();
QByteArray oldMimeFormat = document->mimeType();
QUrl suggestedURL = document->url();
QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export);
mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export);
if (!mimeFilter.contains(oldMimeFormat)) {
dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat;
// --- don't setOutputMimeType in case the user cancels the Save As
// dialog and then tries to just plain Save ---
// suggest a different filename extension (yes, we fortunately don't all live in a world of magic :))
QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).baseName();
if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name
suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first();
suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename);
suggestedURL.setPath(suggestedURL.path() + suggestedFilename);
}
// force the user to choose outputMimeType
saveas = true;
}
bool ret = false;
if (document->url().isEmpty() || isExporting || saveas) {
// if you're just File/Save As'ing to change filter options you
// don't want to be reminded about overwriting files etc.
bool justChangingFilterOptions = false;
KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs");
dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As"));
//qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType());
if (isExporting && !d->lastExportLocation.isEmpty()) {
// Use the location where we last exported to, if it's set, as the opening location for the file dialog
QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath();
// If the document doesn't have a filename yet, use the title
QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).baseName();
// Use the last mimetype we exported to by default
QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat;
QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,");
// Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty
dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true);
dialog.setMimeTypeFilters(mimeFilter, proposedMimeType);
}
else {
// Get the last used location for saving
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
QString proposedPath = group.readEntry("SaveAs", "");
// if that is empty, get the last used location for loading
if (proposedPath.isEmpty()) {
proposedPath = group.readEntry("OpenDocument", "");
}
// If that is empty, too, use the Pictures location.
if (proposedPath.isEmpty()) {
proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
}
// But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise
// open the location where the document currently is.
dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true);
// If exporting, default to all supported file types if user is exporting
QByteArray default_mime_type = "";
if (!isExporting) {
// otherwise use the document's mimetype, or if that is empty, kra, which is the savest.
default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType();
}
dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type));
}
QUrl newURL = QUrl::fromUserInput(dialog.filename());
if (newURL.isLocalFile()) {
QString fn = newURL.toLocalFile();
if (QFileInfo(fn).completeSuffix().isEmpty()) {
fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first());
newURL = QUrl::fromLocalFile(fn);
}
}
if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) {
QString fn = newURL.toLocalFile();
QFileInfo info(fn);
document->documentInfo()->setAboutInfo("title", info.baseName());
}
QByteArray outputFormat = nativeFormat;
QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false);
outputFormat = outputFormatString.toLatin1();
if (!isExporting) {
justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType());
}
else {
QString path = QFileInfo(d->lastExportLocation).absolutePath();
QString filename = QFileInfo(document->url().toLocalFile()).baseName();
justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path)
&& (QFileInfo(newURL.toLocalFile()).baseName() == filename)
&& (outputFormat == d->lastExportedFormat);
}
bool bOk = true;
if (newURL.isEmpty()) {
bOk = false;
}
if (bOk) {
bool wantToSave = true;
// don't change this line unless you know what you're doing :)
if (!justChangingFilterOptions) {
if (!document->isNativeFormat(outputFormat))
wantToSave = true;
}
if (wantToSave) {
if (!isExporting) { // Save As
ret = document->saveAs(newURL, outputFormat, true);
if (ret) {
dbgUI << "Successful Save As!";
KisPart::instance()->addRecentURLToAllMainWindows(newURL);
setReadWrite(true);
} else {
dbgUI << "Failed Save As!";
}
}
else { // Export
ret = document->exportDocument(newURL, outputFormat);
if (ret) {
d->lastExportLocation = newURL.toLocalFile();
d->lastExportedFormat = outputFormat;
}
}
} // if (wantToSave) {
else
ret = false;
} // if (bOk) {
else
ret = false;
} else { // saving
// We cannot "export" into the currently
// opened document. We are not Gimp.
KIS_ASSERT_RECOVER_NOOP(!isExporting);
// be sure document has the correct outputMimeType!
if (document->isModified()) {
ret = document->save(true, 0);
}
if (!ret) {
dbgUI << "Failed Save!";
}
}
updateReloadFileAction(document);
updateCaption();
return ret;
}
void KisMainWindow::undo()
{
if (activeView()) {
activeView()->document()->undoStack()->undo();
}
}
void KisMainWindow::redo()
{
if (activeView()) {
activeView()->document()->undoStack()->redo();
}
}
void KisMainWindow::closeEvent(QCloseEvent *e)
{
if (!KisPart::instance()->closingSession()) {
QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only");
if ((action) && (action->isChecked())) {
action->setChecked(false);
}
// Save session when last window is closed
if (KisPart::instance()->mainwindowCount() == 1) {
bool closeAllowed = KisPart::instance()->closeSession();
if (!closeAllowed) {
e->setAccepted(false);
return;
}
}
}
d->mdiArea->closeAllSubWindows();
QList childrenList = d->mdiArea->subWindowList();
if (childrenList.isEmpty()) {
d->deferredClosingEvent = e;
saveWindowState(true);
} else {
e->setAccepted(false);
}
}
void KisMainWindow::saveWindowSettings()
{
KSharedConfigPtr config = KSharedConfig::openConfig();
if (d->windowSizeDirty ) {
dbgUI << "KisMainWindow::saveWindowSettings";
KConfigGroup group = d->windowStateConfig;
KWindowConfig::saveWindowSize(windowHandle(), group);
config->sync();
d->windowSizeDirty = false;
}
if (!d->activeView || d->activeView->document()) {
// Save toolbar position into the config file of the app, under the doc's component name
KConfigGroup group = d->windowStateConfig;
saveMainWindowSettings(group);
// Save collapsible state of dock widgets
for (QMap::const_iterator i = d->dockWidgetsMap.constBegin();
i != d->dockWidgetsMap.constEnd(); ++i) {
if (i.value()->widget()) {
KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key());
dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden());
dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool());
dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value()));
dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x());
dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y());
dockGroup.writeEntry("width", (int) i.value()->widget()->width());
dockGroup.writeEntry("height", (int) i.value()->widget()->height());
}
}
}
KSharedConfig::openConfig()->sync();
resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down
}
void KisMainWindow::resizeEvent(QResizeEvent * e)
{
d->windowSizeDirty = true;
KXmlGuiWindow::resizeEvent(e);
}
void KisMainWindow::setActiveView(KisView* view)
{
d->activeView = view;
updateCaption();
if (d->undoActionsUpdateManager) {
d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0);
}
d->viewManager->setCurrentView(view);
KisWindowLayoutManager::instance()->activeDocumentChanged(view->document());
}
void KisMainWindow::dragEnterEvent(QDragEnterEvent *event)
{
d->welcomePage->showDropAreaIndicator(true);
if (event->mimeData()->hasUrls() ||
event->mimeData()->hasFormat("application/x-krita-node") ||
event->mimeData()->hasFormat("application/x-qt-image")) {
event->accept();
}
}
void KisMainWindow::dropEvent(QDropEvent *event)
{
d->welcomePage->showDropAreaIndicator(false);
if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) {
Q_FOREACH (const QUrl &url, event->mimeData()->urls()) {
if (url.toLocalFile().endsWith(".bundle")) {
bool r = installBundle(url.toLocalFile());
}
else {
openDocument(url, None);
}
}
}
}
void KisMainWindow::dragMoveEvent(QDragMoveEvent * event)
{
QTabBar *tabBar = d->findTabBarHACK();
if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) {
qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!";
}
if (tabBar && tabBar->isVisible()) {
QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos()));
if (tabBar->rect().contains(pos)) {
const int tabIndex = tabBar->tabAt(pos);
if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) {
d->tabSwitchCompressor->start(tabIndex);
}
} else if (d->tabSwitchCompressor->isActive()) {
d->tabSwitchCompressor->stop();
}
}
}
void KisMainWindow::dragLeaveEvent(QDragLeaveEvent * /*event*/)
{
d->welcomePage->showDropAreaIndicator(false);
if (d->tabSwitchCompressor->isActive()) {
d->tabSwitchCompressor->stop();
}
}
void KisMainWindow::switchTab(int index)
{
QTabBar *tabBar = d->findTabBarHACK();
if (!tabBar) return;
tabBar->setCurrentIndex(index);
}
void KisMainWindow::showWelcomeScreen(bool show)
{
d->widgetStack->setCurrentIndex(!show);
}
void KisMainWindow::slotFileNew()
{
const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import);
KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/"));
startupWidget->setWindowModality(Qt::WindowModal);
startupWidget->setWindowTitle(i18n("Create new document"));
KisConfig cfg(true);
int w = cfg.defImageWidth();
int h = cfg.defImageHeight();
const double resolution = cfg.defImageResolution();
const QString colorModel = cfg.defColorModel();
const QString colorDepth = cfg.defaultColorDepth();
const QString colorProfile = cfg.defColorProfile();
CustomDocumentWidgetItem item;
item.widget = new KisCustomImageWidget(startupWidget,
w,
h,
resolution,
colorModel,
colorDepth,
colorProfile,
i18n("Unnamed"));
item.icon = "document-new";
startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon);
QSize sz = KisClipboard::instance()->clipSize();
if (sz.isValid() && sz.width() != 0 && sz.height() != 0) {
w = sz.width();
h = sz.height();
}
item.widget = new KisImageFromClipboard(startupWidget,
w,
h,
resolution,
colorModel,
colorDepth,
colorProfile,
i18n("Unnamed"));
item.title = i18n("Create from Clipboard");
item.icon = "tab-new";
startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon);
// calls deleteLater
connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*)));
// calls deleteLater
connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl)));
startupWidget->exec();
// Cancel calls deleteLater...
}
void KisMainWindow::slotImportFile()
{
dbgUI << "slotImportFile()";
slotFileOpen(true);
}
void KisMainWindow::slotFileOpen(bool isImporting)
{
QStringList urls = showOpenFileDialog(isImporting);
if (urls.isEmpty())
return;
Q_FOREACH (const QString& url, urls) {
if (!url.isEmpty()) {
OpenFlags flags = isImporting ? Import : None;
bool res = openDocument(QUrl::fromLocalFile(url), flags);
if (!res) {
warnKrita << "Loading" << url << "failed";
}
}
}
}
void KisMainWindow::slotFileOpenRecent(const QUrl &url)
{
(void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None);
}
void KisMainWindow::slotFileSave()
{
if (saveDocument(d->activeView->document(), false, false)) {
emit documentSaved();
}
}
void KisMainWindow::slotFileSaveAs()
{
if (saveDocument(d->activeView->document(), true, false)) {
emit documentSaved();
}
}
void KisMainWindow::slotExportFile()
{
if (saveDocument(d->activeView->document(), true, true)) {
emit documentSaved();
}
}
void KisMainWindow::slotShowSessionManager() {
KisPart::instance()->showSessionManager();
}
KoCanvasResourceProvider *KisMainWindow::resourceManager() const
{
return d->viewManager->resourceProvider()->resourceManager();
}
int KisMainWindow::viewCount() const
{
return d->mdiArea->subWindowList().size();
}
const KConfigGroup &KisMainWindow::windowStateConfig() const
{
return d->windowStateConfig;
}
void KisMainWindow::saveWindowState(bool restoreNormalState)
{
if (restoreNormalState) {
QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only");
if (showCanvasOnly && showCanvasOnly->isChecked()) {
showCanvasOnly->setChecked(false);
}
d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64());
d->windowStateConfig.writeEntry("State", saveState().toBase64());
if (!d->dockerStateBeforeHiding.isEmpty()) {
restoreState(d->dockerStateBeforeHiding);
}
statusBar()->setVisible(true);
menuBar()->setVisible(true);
saveWindowSettings();
} else {
saveMainWindowSettings(d->windowStateConfig);
}
}
bool KisMainWindow::restoreWorkspaceState(const QByteArray &state)
{
QByteArray oldState = saveState();
// needed because otherwise the layout isn't correctly restored in some situations
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
dock->toggleViewAction()->setEnabled(true);
dock->hide();
}
bool success = KXmlGuiWindow::restoreState(state);
if (!success) {
KXmlGuiWindow::restoreState(oldState);
return false;
}
return success;
}
bool KisMainWindow::restoreWorkspace(KisWorkspaceResource *workspace)
{
bool success = restoreWorkspaceState(workspace->dockerState());
if (activeKisView()) {
activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace);
}
return success;
}
QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other)
{
QByteArray currentWorkspace = saveState();
if (!d->workspaceBorrowedBy.isNull()) {
if (other->id() == d->workspaceBorrowedBy) {
// We're swapping our original workspace back
d->workspaceBorrowedBy = QUuid();
return currentWorkspace;
} else {
// Get our original workspace back before swapping with a third window
KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy);
if (borrower) {
QByteArray originalLayout = borrower->borrowWorkspace(this);
borrower->restoreWorkspaceState(currentWorkspace);
d->workspaceBorrowedBy = other->id();
return originalLayout;
}
}
}
d->workspaceBorrowedBy = other->id();
return currentWorkspace;
}
void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b)
{
QByteArray workspaceA = a->borrowWorkspace(b);
QByteArray workspaceB = b->borrowWorkspace(a);
a->restoreWorkspaceState(workspaceB);
b->restoreWorkspaceState(workspaceA);
}
KisViewManager *KisMainWindow::viewManager() const
{
return d->viewManager;
}
void KisMainWindow::slotDocumentInfo()
{
if (!d->activeView->document())
return;
KoDocumentInfo *docInfo = d->activeView->document()->documentInfo();
if (!docInfo)
return;
KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo);
if (dlg->exec()) {
if (dlg->isDocumentSaved()) {
d->activeView->document()->setModified(false);
} else {
d->activeView->document()->setModified(true);
}
d->activeView->document()->setTitleModified();
}
delete dlg;
}
bool KisMainWindow::slotFileCloseAll()
{
Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
if (subwin) {
if(!subwin->close())
return false;
}
}
updateCaption();
return true;
}
void KisMainWindow::slotFileQuit()
{
KisPart::instance()->closeSession();
}
void KisMainWindow::slotFilePrint()
{
if (!activeView())
return;
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return;
applyDefaultSettings(printJob->printer());
QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this );
if (printDialog && printDialog->exec() == QDialog::Accepted) {
printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point);
printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()),
activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())),
QPrinter::Inch);
printJob->startPrinting(KisPrintJob::DeleteWhenDone);
}
else {
delete printJob;
}
delete printDialog;
}
void KisMainWindow::slotFilePrintPreview()
{
if (!activeView())
return;
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return;
/* Sets the startPrinting() slot to be blocking.
The Qt print-preview dialog requires the printing to be completely blocking
and only return when the full document has been printed.
By default the KisPrintingDialog is non-blocking and
multithreading, setting blocking to true will allow it to be used in the preview dialog */
printJob->setProperty("blocking", true);
QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this);
printJob->setParent(preview); // will take care of deleting the job
connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting()));
preview->exec();
delete preview;
}
KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName)
{
if (!activeView())
return 0;
if (!activeView()->document())
return 0;
KoPageLayout pageLayout;
pageLayout.width = 0;
pageLayout.height = 0;
pageLayout.topMargin = 0;
pageLayout.bottomMargin = 0;
pageLayout.leftMargin = 0;
pageLayout.rightMargin = 0;
if (pdfFileName.isEmpty()) {
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
QString defaultDir = group.readEntry("SavePdfDialog");
if (defaultDir.isEmpty())
defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QUrl startUrl = QUrl::fromLocalFile(defaultDir);
KisDocument* pDoc = d->activeView->document();
/** if document has a file name, take file name and replace extension with .pdf */
if (pDoc && pDoc->url().isValid()) {
startUrl = pDoc->url();
QString fileName = startUrl.toLocalFile();
fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" );
startUrl = startUrl.adjusted(QUrl::RemoveFilename);
startUrl.setPath(startUrl.path() + fileName );
}
QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout));
layoutDlg->setWindowModality(Qt::WindowModal);
if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) {
delete layoutDlg;
return 0;
}
pageLayout = layoutDlg->pageLayout();
delete layoutDlg;
KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument");
dialog.setCaption(i18n("Export as PDF"));
dialog.setDefaultDir(startUrl.toLocalFile());
dialog.setMimeTypeFilters(QStringList() << "application/pdf");
QUrl url = QUrl::fromUserInput(dialog.filename());
pdfFileName = url.toLocalFile();
if (pdfFileName.isEmpty())
return 0;
}
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return 0;
if (isHidden()) {
printJob->setProperty("noprogressdialog", true);
}
applyDefaultSettings(printJob->printer());
// TODO for remote files we have to first save locally and then upload.
printJob->printer().setOutputFileName(pdfFileName);
printJob->printer().setDocName(pdfFileName);
printJob->printer().setColorMode(QPrinter::Color);
if (pageLayout.format == KoPageFormat::CustomSize) {
printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter);
} else {
printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format));
}
printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter);
switch (pageLayout.orientation) {
case KoPageFormat::Portrait:
printJob->printer().setOrientation(QPrinter::Portrait);
break;
case KoPageFormat::Landscape:
printJob->printer().setOrientation(QPrinter::Landscape);
break;
}
//before printing check if the printer can handle printing
if (!printJob->canPrint()) {
QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file"));
}
printJob->startPrinting(KisPrintJob::DeleteWhenDone);
return printJob;
}
void KisMainWindow::importAnimation()
{
if (!activeView()) return;
KisDocument *document = activeView()->document();
if (!document) return;
KisDlgImportImageSequence dlg(this, document);
if (dlg.exec() == QDialog::Accepted) {
QStringList files = dlg.files();
int firstFrame = dlg.firstFrame();
int step = dlg.step();
KoUpdaterPtr updater =
!document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0;
KisAnimationImporter importer(document->image(), updater);
KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step);
if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) {
QString msg = KisImportExportFilter::conversionStatusString(status);
if (!msg.isEmpty())
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg));
}
activeView()->canvasBase()->refetchDataFromImage();
}
}
void KisMainWindow::slotConfigureToolbars()
{
saveWindowState();
KEditToolBar edit(factory(), this);
connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig()));
(void) edit.exec();
applyToolBarLayout();
}
void KisMainWindow::slotNewToolbarConfig()
{
applyMainWindowSettings(d->windowStateConfig);
KXMLGUIFactory *factory = guiFactory();
Q_UNUSED(factory);
// Check if there's an active view
if (!d->activeView)
return;
plugActionList("toolbarlist", d->toolbarList);
applyToolBarLayout();
}
void KisMainWindow::slotToolbarToggled(bool toggle)
{
//dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true;
// The action (sender) and the toolbar have the same name
KToolBar * bar = toolBar(sender()->objectName());
if (bar) {
if (toggle) {
bar->show();
}
else {
bar->hide();
}
if (d->activeView && d->activeView->document()) {
saveWindowState();
}
} else
warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!";
}
void KisMainWindow::viewFullscreen(bool fullScreen)
{
KisConfig cfg(false);
cfg.setFullscreenMode(fullScreen);
if (fullScreen) {
setWindowState(windowState() | Qt::WindowFullScreen); // set
} else {
setWindowState(windowState() & ~Qt::WindowFullScreen); // reset
}
}
void KisMainWindow::setMaxRecentItems(uint _number)
{
d->recentFiles->setMaxItems(_number);
}
void KisMainWindow::slotReloadFile()
{
KisDocument* document = d->activeView->document();
if (!document || document->url().isEmpty())
return;
if (document->isModified()) {
bool ok = QMessageBox::question(this,
i18nc("@title:window", "Krita"),
i18n("You will lose all changes made since your last save\n"
"Do you want to continue?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes;
if (!ok)
return;
}
QUrl url = document->url();
saveWindowSettings();
if (!document->reload()) {
QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document"));
}
return;
}
QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory)
{
QDockWidget* dockWidget = 0;
bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false);
if (!d->dockWidgetsMap.contains(factory->id())) {
dockWidget = factory->createDockWidget();
// It is quite possible that a dock factory cannot create the dock; don't
// do anything in that case.
if (!dockWidget) {
warnKrita << "Could not create docker for" << factory->id();
return 0;
}
dockWidget->setFont(KoDockRegistry::dockFont());
dockWidget->setObjectName(factory->id());
dockWidget->setParent(this);
if (lockAllDockers) {
if (dockWidget->titleBarWidget()) {
dockWidget->titleBarWidget()->setVisible(false);
}
dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures);
}
if (dockWidget->widget() && dockWidget->widget()->layout())
dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1);
Qt::DockWidgetArea side = Qt::RightDockWidgetArea;
bool visible = true;
switch (factory->defaultDockPosition()) {
case KoDockFactoryBase::DockTornOff:
dockWidget->setFloating(true); // position nicely?
break;
case KoDockFactoryBase::DockTop:
side = Qt::TopDockWidgetArea; break;
case KoDockFactoryBase::DockLeft:
side = Qt::LeftDockWidgetArea; break;
case KoDockFactoryBase::DockBottom:
side = Qt::BottomDockWidgetArea; break;
case KoDockFactoryBase::DockRight:
side = Qt::RightDockWidgetArea; break;
case KoDockFactoryBase::DockMinimized:
default:
side = Qt::RightDockWidgetArea;
visible = false;
}
KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id());
side = static_cast(group.readEntry("DockArea", static_cast(side)));
if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea;
addDockWidget(side, dockWidget);
if (!visible) {
dockWidget->hide();
}
d->dockWidgetsMap.insert(factory->id(), dockWidget);
}
else {
dockWidget = d->dockWidgetsMap[factory->id()];
}
#ifdef Q_OS_OSX
dockWidget->setAttribute(Qt::WA_MacSmallSize, true);
#endif
dockWidget->setFont(KoDockRegistry::dockFont());
connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts()));
return dockWidget;
}
void KisMainWindow::forceDockTabFonts()
{
Q_FOREACH (QObject *child, children()) {
if (child->inherits("QTabBar")) {
((QTabBar *)child)->setFont(KoDockRegistry::dockFont());
}
}
}
QList KisMainWindow::dockWidgets() const
{
return d->dockWidgetsMap.values();
}
QDockWidget* KisMainWindow::dockWidget(const QString &id)
{
if (!d->dockWidgetsMap.contains(id)) return 0;
return d->dockWidgetsMap[id];
}
QList KisMainWindow::canvasObservers() const
{
QList observers;
Q_FOREACH (QDockWidget *docker, dockWidgets()) {
KoCanvasObserverBase *observer = dynamic_cast(docker);
if (observer) {
observers << observer;
}
else {
warnKrita << docker << "is not a canvas observer";
}
}
return observers;
}
void KisMainWindow::toggleDockersVisibility(bool visible)
{
if (!visible) {
d->dockerStateBeforeHiding = saveState();
Q_FOREACH (QObject* widget, children()) {
if (widget->inherits("QDockWidget")) {
QDockWidget* dw = static_cast(widget);
if (dw->isVisible()) {
dw->hide();
}
}
}
}
else {
restoreState(d->dockerStateBeforeHiding);
}
}
void KisMainWindow::slotDocumentTitleModified()
{
updateCaption();
updateReloadFileAction(d->activeView ? d->activeView->document() : 0);
}
void KisMainWindow::subWindowActivated()
{
bool enabled = (activeKisView() != 0);
d->mdiCascade->setEnabled(enabled);
d->mdiNextWindow->setEnabled(enabled);
d->mdiPreviousWindow->setEnabled(enabled);
d->mdiTile->setEnabled(enabled);
d->close->setEnabled(enabled);
d->closeAll->setEnabled(enabled);
setActiveSubWindow(d->mdiArea->activeSubWindow());
Q_FOREACH (QToolBar *tb, toolBars()) {
if (tb->objectName() == "BrushesAndStuff") {
tb->setEnabled(enabled);
}
}
/**
* Qt has a weirdness, it has hardcoded shortcuts added to an action
* in the window menu. We need to reset the shortcuts for that menu
* to nothing, otherwise the shortcuts cannot be made configurable.
*
* See: https://bugs.kde.org/show_bug.cgi?id=352205
* https://bugs.kde.org/show_bug.cgi?id=375524
* https://bugs.kde.org/show_bug.cgi?id=398729
*/
QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow();
if (subWindow) {
QMenu *menu = subWindow->systemMenu();
if (menu) {
Q_FOREACH (QAction *action, menu->actions()) {
action->setShortcut(QKeySequence());
action->deleteLater();
}
}
}
updateCaption();
d->actionManager()->updateGUI();
}
void KisMainWindow::windowFocused()
{
/**
* Notify selection manager so that it could update selection mask overlay
*/
viewManager()->selectionManager()->selectionChanged();
auto *kisPart = KisPart::instance();
auto *layoutManager = KisWindowLayoutManager::instance();
if (!layoutManager->primaryWorkspaceFollowsFocus()) return;
QUuid primary = layoutManager->primaryWindowId();
if (primary.isNull()) return;
if (d->id == primary) {
if (!d->workspaceBorrowedBy.isNull()) {
KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy);
if (!borrower) return;
swapWorkspaces(this, borrower);
}
} else {
if (d->workspaceBorrowedBy == primary) return;
KisMainWindow *primaryWindow = kisPart->windowById(primary);
if (!primaryWindow) return;
swapWorkspaces(this, primaryWindow);
}
}
void KisMainWindow::updateWindowMenu()
{
QMenu *menu = d->windowMenu->menu();
menu->clear();
menu->addAction(d->newWindow);
menu->addAction(d->documentMenu);
QMenu *docMenu = d->documentMenu->menu();
docMenu->clear();
Q_FOREACH (QPointer doc, KisPart::instance()->documents()) {
if (doc) {
QString title = doc->url().toDisplayString();
if (title.isEmpty() && doc->image()) {
title = doc->image()->objectName();
}
QAction *action = docMenu->addAction(title);
action->setIcon(qApp->windowIcon());
connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map()));
d->documentMapper->setMapping(action, doc);
}
}
menu->addAction(d->workspaceMenu);
QMenu *workspaceMenu = d->workspaceMenu->menu();
workspaceMenu->clear();
auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources();
auto m_this = this;
for (auto &w : workspaces) {
auto action = workspaceMenu->addAction(w->name());
connect(action, &QAction::triggered, this, [=]() {
m_this->restoreWorkspace(w);
});
}
workspaceMenu->addSeparator();
connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")),
&QAction::triggered,
this,
[&]() {
QString extensions = d->workspacemodel->extensions();
QStringList mimeTypes;
for(const QString &suffix : extensions.split(":")) {
mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix);
}
KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument");
dialog.setMimeTypeFilters(mimeTypes);
dialog.setCaption(i18nc("@title:window", "Choose File to Add"));
QString filename = dialog.filename();
d->workspacemodel->importResourceFile(filename);
});
connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")),
&QAction::triggered,
[=]() {
QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."),
i18nc("@label:textbox", "Name:"));
if (name.isEmpty()) return;
auto rserver = KisResourceServerProvider::instance()->workspaceServer();
KisWorkspaceResource* workspace = new KisWorkspaceResource("");
workspace->setDockerState(m_this->saveState());
d->viewManager->resourceProvider()->notifySavingWorkspace(workspace);
workspace->setValid(true);
QString saveLocation = rserver->saveLocation();
bool newName = false;
if(name.isEmpty()) {
newName = true;
name = i18n("Workspace");
}
QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension());
int i = 1;
while (fileInfo.exists()) {
fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension());
i++;
}
workspace->setFilename(fileInfo.filePath());
if(newName) {
name = i18n("Workspace %1", i);
}
workspace->setName(name);
rserver->addResource(workspace);
});
// TODO: What to do about delete?
// workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace..."));
menu->addSeparator();
menu->addAction(d->close);
menu->addAction(d->closeAll);
if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) {
menu->addSeparator();
menu->addAction(d->mdiTile);
menu->addAction(d->mdiCascade);
}
menu->addSeparator();
menu->addAction(d->mdiNextWindow);
menu->addAction(d->mdiPreviousWindow);
menu->addSeparator();
QList windows = d->mdiArea->subWindowList();
for (int i = 0; i < windows.size(); ++i) {
QPointerchild = qobject_cast(windows.at(i)->widget());
if (child && child->document()) {
QString text;
if (i < 9) {
text = i18n("&%1 %2", i + 1, child->document()->url().toDisplayString());
}
else {
text = i18n("%1 %2", i + 1, child->document()->url().toDisplayString());
}
QAction *action = menu->addAction(text);
action->setIcon(qApp->windowIcon());
action->setCheckable(true);
action->setChecked(child == activeKisView());
connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map()));
d->windowMapper->setMapping(action, windows.at(i));
}
}
bool showMdiArea = windows.count( ) > 0;
if (!showMdiArea) {
showWelcomeScreen(true); // see workaround in function in header
// keep the recent file list updated when going back to welcome screen
reloadRecentFileList();
d->welcomePage->populateRecentDocuments();
}
// enable/disable the toolbox docker if there are no documents open
Q_FOREACH (QObject* widget, children()) {
if (widget->inherits("QDockWidget")) {
QDockWidget* dw = static_cast(widget);
if ( dw->objectName() == "ToolBox") {
dw->setEnabled(showMdiArea);
}
}
}
updateCaption();
}
void KisMainWindow::setActiveSubWindow(QWidget *window)
{
if (!window) return;
QMdiSubWindow *subwin = qobject_cast(window);
//dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow;
if (subwin && subwin != d->activeSubWindow) {
KisView *view = qobject_cast(subwin->widget());
//dbgKrita << "\t" << view << activeView();
if (view && view != activeView()) {
d->mdiArea->setActiveSubWindow(subwin);
setActiveView(view);
}
d->activeSubWindow = subwin;
}
updateWindowMenu();
d->actionManager()->updateGUI();
}
void KisMainWindow::configChanged()
{
KisConfig cfg(true);
QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView);
d->mdiArea->setViewMode(viewMode);
Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL()));
subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL()));
/**
* Dirty workaround for a bug in Qt (checked on Qt 5.6.1):
*
* If you make a window "Show on top" and then switch to the tabbed mode
* the window will contiue to be painted in its initial "mid-screen"
* position. It will persist here until you explicitly switch to its tab.
*/
if (viewMode == QMdiArea::TabbedView) {
Qt::WindowFlags oldFlags = subwin->windowFlags();
Qt::WindowFlags flags = oldFlags;
flags &= ~Qt::WindowStaysOnTopHint;
flags &= ~Qt::WindowStaysOnBottomHint;
if (flags != oldFlags) {
subwin->setWindowFlags(flags);
subwin->showMaximized();
}
}
}
KConfigGroup group( KSharedConfig::openConfig(), "theme");
d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark"));
d->actionManager()->updateGUI();
QBrush brush(cfg.getMDIBackgroundColor());
d->mdiArea->setBackground(brush);
QString backgroundImage = cfg.getMDIBackgroundImage();
if (backgroundImage != "") {
QImage image(backgroundImage);
QBrush brush(image);
d->mdiArea->setBackground(brush);
}
d->mdiArea->update();
}
KisView* KisMainWindow::newView(QObject *document)
{
KisDocument *doc = qobject_cast(document);
KisView *view = addViewAndNotifyLoadingCompleted(doc);
d->actionManager()->updateGUI();
return view;
}
void KisMainWindow::newWindow()
{
KisMainWindow *mainWindow = KisPart::instance()->createMainWindow();
mainWindow->initializeGeometry();
mainWindow->show();
}
void KisMainWindow::closeCurrentWindow()
{
if (d->mdiArea->currentSubWindow()) {
d->mdiArea->currentSubWindow()->close();
d->actionManager()->updateGUI();
}
}
void KisMainWindow::checkSanity()
{
// print error if the lcms engine is not available
if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) {
// need to wait 1 event since exiting here would not work.
m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now.");
m_dieOnError = true;
QTimer::singleShot(0, this, SLOT(showErrorAndDie()));
return;
}
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
if (rserver->resources().isEmpty()) {
m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now.");
m_dieOnError = true;
QTimer::singleShot(0, this, SLOT(showErrorAndDie()));
return;
}
}
void KisMainWindow::showErrorAndDie()
{
QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage);
if (m_dieOnError) {
exit(10);
}
}
void KisMainWindow::showAboutApplication()
{
KisAboutApplication dlg(this);
dlg.exec();
}
QPointer KisMainWindow::activeKisView()
{
if (!d->mdiArea) return 0;
QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow();
//dbgKrita << "activeKisView" << activeSubWindow;
if (!activeSubWindow) return 0;
return qobject_cast(activeSubWindow->widget());
}
void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList)
{
KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController());
bool isOurOwnView = false;
Q_FOREACH (QPointer view, KisPart::instance()->views()) {
if (view && view->canvasController() == controller) {
isOurOwnView = view->mainWindow() == this;
}
}
if (!isOurOwnView) return;
Q_FOREACH (QWidget *w, optionWidgetList) {
#ifdef Q_OS_OSX
w->setAttribute(Qt::WA_MacSmallSize, true);
#endif
w->setFont(KoDockRegistry::dockFont());
}
if (d->toolOptionsDocker) {
d->toolOptionsDocker->setOptionWidgets(optionWidgetList);
}
else {
d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList);
}
}
void KisMainWindow::applyDefaultSettings(QPrinter &printer) {
if (!d->activeView) return;
QString title = d->activeView->document()->documentInfo()->aboutInfo("title");
if (title.isEmpty()) {
QFileInfo info(d->activeView->document()->url().fileName());
title = info.baseName();
}
if (title.isEmpty()) {
// #139905
title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(),
QLocale().toString(QDate::currentDate(), QLocale::ShortFormat));
}
printer.setDocName(title);
}
void KisMainWindow::createActions()
{
KisActionManager *actionManager = d->actionManager();
actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew()));
actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen()));
actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit()));
actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars()));
d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool)));
d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection());
connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles()));
KSharedConfigPtr configPtr = KSharedConfig::openConfig();
d->recentFiles->loadEntries(configPtr->group("RecentFiles"));
d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave()));
d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs()));
d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint()));
// d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview()));
// d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo()));
d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo()));
d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo));
d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0);
// d->exportPdf = actionManager->createAction("file_export_pdf");
// connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf()));
d->importAnimation = actionManager->createAction("file_import_animation");
connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation()));
d->closeAll = actionManager->createAction("file_close_all");
connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll()));
// d->reloadFile = actionManager->createAction("file_reload_file");
// d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED);
// connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile()));
d->importFile = actionManager->createAction("file_import_file");
connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile()));
d->exportFile = actionManager->createAction("file_export_file");
connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile()));
/* The following entry opens the document information dialog. Since the action is named so it
intends to show data this entry should not have a trailing ellipses (...). */
d->showDocumentInfo = actionManager->createAction("file_documentinfo");
connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo()));
d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this));
d->themeManager->registerThemeActions(actionCollection());
connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged()));
connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors()));
d->toggleDockers = actionManager->createAction("view_toggledockers");
KisConfig(true).showDockers(true);
d->toggleDockers->setChecked(true);
connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool)));
actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu);
actionCollection()->addAction("window", d->windowMenu);
d->mdiCascade = actionManager->createAction("windows_cascade");
connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows()));
d->mdiTile = actionManager->createAction("windows_tile");
connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows()));
d->mdiNextWindow = actionManager->createAction("windows_next");
connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow()));
d->mdiPreviousWindow = actionManager->createAction("windows_previous");
connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow()));
d->newWindow = actionManager->createAction("view_newwindow");
connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow()));
d->close = actionManager->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow()));
d->showSessionManager = actionManager->createAction("file_sessions");
connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager()));
actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences()));
for (int i = 0; i < 2; i++) {
d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer"));
d->expandingSpacers[i]->setDefaultWidget(new QWidget(this));
d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]);
}
}
void KisMainWindow::applyToolBarLayout()
{
const bool isPlastiqueStyle = style()->objectName() == "plastique";
Q_FOREACH (KToolBar *toolBar, toolBars()) {
toolBar->layout()->setSpacing(4);
if (isPlastiqueStyle) {
toolBar->setContentsMargins(0, 0, 0, 2);
}
//Hide text for buttons with an icon in the toolbar
Q_FOREACH (QAction *ac, toolBar->actions()){
if (ac->icon().pixmap(QSize(1,1)).isNull() == false){
ac->setPriority(QAction::LowPriority);
}else {
ac->setIcon(QIcon());
}
}
}
}
void KisMainWindow::initializeGeometry()
{
// if the user didn's specify the geometry on the command line (does anyone do that still?),
// we first figure out some good default size and restore the x,y position. See bug 285804Z.
KConfigGroup cfg = d->windowStateConfig;
QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray()));
if (!restoreGeometry(geom)) {
const int scnum = QApplication::desktop()->screenNumber(parentWidget());
QRect desk = QApplication::desktop()->availableGeometry(scnum);
// if the desktop is virtual then use virtual screen size
if (QApplication::desktop()->isVirtualDesktop()) {
desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum));
}
quint32 x = desk.x();
quint32 y = desk.y();
quint32 w = 0;
quint32 h = 0;
// Default size -- maximize on small screens, something useful on big screens
const int deskWidth = desk.width();
if (deskWidth > 1024) {
// a nice width, and slightly less than total available
// height to compensate for the window decs
w = (deskWidth / 3) * 2;
h = (desk.height() / 3) * 2;
}
else {
w = desk.width();
h = desk.height();
}
x += (desk.width() - w) / 2;
y += (desk.height() - h) / 2;
move(x,y);
setGeometry(geometry().x(), geometry().y(), w, h);
}
d->fullScreenMode->setChecked(isFullScreen());
}
void KisMainWindow::showManual()
{
QDesktopServices::openUrl(QUrl("https://docs.krita.org"));
}
void KisMainWindow::moveEvent(QMoveEvent *e)
{
/**
* For checking if the display number has changed or not we should always use
* positional overload, not using QWidget overload. Otherwise we might get
* inconsistency, because screenNumber(widget) can return -1, but screenNumber(pos)
* will always return the nearest screen.
*/
const int oldScreen = qApp->desktop()->screenNumber(e->oldPos());
const int newScreen = qApp->desktop()->screenNumber(e->pos());
if (oldScreen != newScreen) {
KisConfigNotifier::instance()->notifyConfigChanged();
}
}
#include
diff --git a/libs/ui/KisPaletteEditor.cpp b/libs/ui/KisPaletteEditor.cpp
index e1008b5769..690894f4c4 100644
--- a/libs/ui/KisPaletteEditor.cpp
+++ b/libs/ui/KisPaletteEditor.cpp
@@ -1,664 +1,663 @@
/*
* 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/KisWindowLayoutResource.cpp b/libs/ui/KisWindowLayoutResource.cpp
index 4f85ead731..1f182c59b1 100644
--- a/libs/ui/KisWindowLayoutResource.cpp
+++ b/libs/ui/KisWindowLayoutResource.cpp
@@ -1,387 +1,387 @@
/*
* Copyright (c) 2018 Jouni Pentikäinen
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "KisWindowLayoutResource.h"
#include "KisWindowLayoutManager.h"
#include
#include
#include
#include
#include
-#include
+#include
#include
#include
#include
#include
#include
static const int WINDOW_LAYOUT_VERSION = 1;
struct KisWindowLayoutResource::Private
{
struct Window {
QUuid windowId;
QByteArray geometry;
QByteArray windowState;
int screen = -1;
Qt::WindowStates stateFlags = Qt::WindowNoState;
};
QVector windows;
bool showImageInAllWindows;
bool primaryWorkspaceFollowsFocus;
QUuid primaryWindow;
Private() = default;
explicit Private(QVector windows)
: windows(std::move(windows))
{}
void openNecessaryWindows(QList> ¤tWindows) {
auto *kisPart = KisPart::instance();
Q_FOREACH(const Window &window, windows) {
QPointer mainWindow = kisPart->windowById(window.windowId);
if (mainWindow.isNull()) {
mainWindow = kisPart->createMainWindow(window.windowId);
currentWindows.append(mainWindow);
mainWindow->show();
}
}
}
void closeUnneededWindows(QList> ¤tWindows) {
QVector> windowsToClose;
Q_FOREACH(KisMainWindow *mainWindow, currentWindows) {
bool keep = false;
Q_FOREACH(const Window &window, windows) {
if (window.windowId == mainWindow->id()) {
keep = true;
break;
}
}
if (!keep) {
windowsToClose.append(mainWindow);
// Set the window hidden to prevent "show image in all windows" feature from opening new views on it
// while we migrate views onto the remaining windows
mainWindow->hide();
}
}
migrateViewsFromClosingWindows(windowsToClose);
Q_FOREACH(QPointer mainWindow, windowsToClose) {
mainWindow->close();
}
}
void migrateViewsFromClosingWindows(QVector> &closingWindows) const
{
auto *kisPart = KisPart::instance();
KisMainWindow *migrationTarget = nullptr;
Q_FOREACH(KisMainWindow *mainWindow, kisPart->mainWindows()) {
if (!closingWindows.contains(mainWindow)) {
migrationTarget = mainWindow;
break;
}
}
if (!migrationTarget) {
qWarning() << "Problem: window layout with no windows would leave user with zero main windows.";
migrationTarget = closingWindows.takeLast();
migrationTarget->show();
}
QVector visibleDocuments;
Q_FOREACH(KisView *view, kisPart->views()) {
KisMainWindow *window = view->mainWindow();
if (!closingWindows.contains(window)) {
visibleDocuments.append(view->document());
}
}
Q_FOREACH(KisDocument *document, kisPart->documents()) {
if (!visibleDocuments.contains(document)) {
visibleDocuments.append(document);
migrationTarget->newView(document);
}
}
}
QList getScreensInConsistentOrder() {
QList screens = QGuiApplication::screens();
std::sort(screens.begin(), screens.end(), [](const QScreen *a, const QScreen *b) {
QRect aRect = a->geometry();
QRect bRect = b->geometry();
if (aRect.y() == bRect.y()) return aRect.x() < bRect.x();
return (aRect.y() < aRect.y());
});
return screens;
}
};
KisWindowLayoutResource::KisWindowLayoutResource(const QString &filename)
: KoResource(filename)
, d(new Private)
{}
KisWindowLayoutResource::~KisWindowLayoutResource()
{}
KisWindowLayoutResource * KisWindowLayoutResource::fromCurrentWindows(
const QString &filename, const QList> &mainWindows, bool showImageInAllWindows,
bool primaryWorkspaceFollowsFocus, KisMainWindow *primaryWindow
)
{
auto resource = new KisWindowLayoutResource(filename);
resource->setWindows(mainWindows);
resource->d->showImageInAllWindows = showImageInAllWindows;
resource->d->primaryWorkspaceFollowsFocus = primaryWorkspaceFollowsFocus;
resource->d->primaryWindow = primaryWindow->id();
return resource;
}
void KisWindowLayoutResource::applyLayout()
{
auto *kisPart = KisPart::instance();
auto *layoutManager= KisWindowLayoutManager::instance();
layoutManager->setLastUsedLayout(this);
QList> currentWindows = kisPart->mainWindows();
if (d->windows.isEmpty()) {
// No windows defined (e.g. fresh new session). Leave things as they are, but make sure there's at least one visible main window
if (kisPart->mainwindowCount() == 0) {
kisPart->createMainWindow();
} else {
kisPart->mainWindows().first()->show();
}
} else {
d->openNecessaryWindows(currentWindows);
d->closeUnneededWindows(currentWindows);
}
// Wait for the windows to finish opening / closing before applying saved geometry.
// If we don't, the geometry may get reset after we apply it.
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
Q_FOREACH(const auto &window, d->windows) {
QPointer mainWindow = kisPart->windowById(window.windowId);
KIS_SAFE_ASSERT_RECOVER_BREAK(mainWindow);
mainWindow->restoreGeometry(window.geometry);
mainWindow->restoreWorkspaceState(window.windowState);
}
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QList screens = d->getScreensInConsistentOrder();
Q_FOREACH(const auto &window, d->windows) {
if (window.screen >= 0 && window.screen < screens.size()) {
QPointer mainWindow = kisPart->windowById(window.windowId);
KIS_SAFE_ASSERT_RECOVER_BREAK(mainWindow);
QWindow *windowHandle = mainWindow->windowHandle();
if (screens.indexOf(windowHandle->screen()) != window.screen) {
QScreen *screen = screens[window.screen];
windowHandle->setScreen(screen);
windowHandle->setPosition(screen->availableGeometry().topLeft());
}
if (window.stateFlags) {
mainWindow->setWindowState(window.stateFlags);
}
}
}
layoutManager->setShowImageInAllWindowsEnabled(d->showImageInAllWindows);
layoutManager->setPrimaryWorkspaceFollowsFocus(d->primaryWorkspaceFollowsFocus, d->primaryWindow);
}
bool KisWindowLayoutResource::save()
{
if (filename().isEmpty())
return false;
QFile file(filename());
file.open(QIODevice::WriteOnly);
bool res = saveToDevice(&file);
file.close();
return res;
}
bool KisWindowLayoutResource::load()
{
if (filename().isEmpty())
return false;
QFile file(filename());
if (file.size() == 0) return false;
if (!file.open(QIODevice::ReadOnly)) {
warnKrita << "Can't open file " << filename();
return false;
}
bool res = loadFromDevice(&file);
file.close();
return res;
}
bool KisWindowLayoutResource::saveToDevice(QIODevice *dev) const
{
QDomDocument doc;
QDomElement root = doc.createElement("WindowLayout");
root.setAttribute("name", name());
root.setAttribute("version", WINDOW_LAYOUT_VERSION);
saveXml(doc, root);
doc.appendChild(root);
QTextStream textStream(dev);
textStream.setCodec("UTF-8");
doc.save(textStream, 4);
KoResource::saveToDevice(dev);
return true;
}
bool KisWindowLayoutResource::loadFromDevice(QIODevice *dev)
{
QDomDocument doc;
if (!doc.setContent(dev)) {
return false;
}
QDomElement element = doc.documentElement();
setName(element.attribute("name"));
d->windows.clear();
loadXml(element);
setValid(true);
return true;
}
void KisWindowLayoutResource::saveXml(QDomDocument &doc, QDomElement &root) const
{
root.setAttribute("showImageInAllWindows", (int)d->showImageInAllWindows);
root.setAttribute("primaryWorkspaceFollowsFocus", (int)d->primaryWorkspaceFollowsFocus);
root.setAttribute("primaryWindow", d->primaryWindow.toString());
Q_FOREACH(const auto &window, d->windows) {
QDomElement elem = doc.createElement("window");
elem.setAttribute("id", window.windowId.toString());
if (window.screen >= 0) {
elem.setAttribute("screen", window.screen);
}
if (window.stateFlags & Qt::WindowMaximized) {
elem.setAttribute("maximized", "1");
}
QDomElement geometry = doc.createElement("geometry");
geometry.appendChild(doc.createCDATASection(window.geometry.toBase64()));
elem.appendChild(geometry);
QDomElement state = doc.createElement("windowState");
state.appendChild(doc.createCDATASection(window.windowState.toBase64()));
elem.appendChild(state);
root.appendChild(elem);
}
}
void KisWindowLayoutResource::loadXml(const QDomElement &element) const
{
d->showImageInAllWindows = KisDomUtils::toInt(element.attribute("showImageInAllWindows", "0"));
d->primaryWorkspaceFollowsFocus = KisDomUtils::toInt(element.attribute("primaryWorkspaceFollowsFocus", "0"));
d->primaryWindow = element.attribute("primaryWindow");
for (auto windowElement = element.firstChildElement("window");
!windowElement.isNull();
windowElement = windowElement.nextSiblingElement("window")) {
Private::Window window;
window.windowId = QUuid(windowElement.attribute("id", QUuid().toString()));
if (window.windowId.isNull()) {
window.windowId = QUuid::createUuid();
}
window.screen = windowElement.attribute("screen", "-1").toInt();
if (windowElement.attribute("maximized", "0") != "0") {
window.stateFlags |= Qt::WindowMaximized;
}
QDomElement geometry = windowElement.firstChildElement("geometry");
QDomElement state = windowElement.firstChildElement("windowState");
window.geometry = QByteArray::fromBase64(geometry.text().toLatin1());
window.windowState = QByteArray::fromBase64(state.text().toLatin1());
d->windows.append(window);
}
}
QString KisWindowLayoutResource::defaultFileExtension() const
{
return QString(".kwl");
}
void KisWindowLayoutResource::setWindows(const QList> &mainWindows)
{
d->windows.clear();
QList screens = d->getScreensInConsistentOrder();
Q_FOREACH(auto window, mainWindows) {
if (!window->isVisible()) continue;
Private::Window state;
state.windowId = window->id();
state.geometry = window->saveGeometry();
state.windowState = window->saveState();
state.stateFlags = window->windowState();
QWindow *windowHandle = window->windowHandle();
if (windowHandle) {
int index = screens.indexOf(windowHandle->screen());
if (index >= 0) {
state.screen = index;
}
}
d->windows.append(state);
}
}
diff --git a/libs/ui/dialogs/KisSessionManagerDialog.cpp b/libs/ui/dialogs/KisSessionManagerDialog.cpp
index 67fdada335..5d140d1956 100644
--- a/libs/ui/dialogs/KisSessionManagerDialog.cpp
+++ b/libs/ui/dialogs/KisSessionManagerDialog.cpp
@@ -1,148 +1,148 @@
/*
* Copyright (c) 2018 Jouni Pentikäinen
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include
#include
-#include
-#include
+#include
+#include
#include
#include "KisSessionManagerDialog.h"
KisSessionManagerDialog::KisSessionManagerDialog(QWidget *parent)
: QDialog(parent)
{
setupUi(this);
updateSessionList();
connect(btnNew, SIGNAL(clicked()), this, SLOT(slotNewSession()));
connect(btnRename, SIGNAL(clicked()), this, SLOT(slotRenameSession()));
connect(btnSwitchTo, SIGNAL(clicked()), this, SLOT(slotSwitchSession()));
connect(btnDelete, SIGNAL(clicked()), this, SLOT(slotDeleteSession()));
connect(btnClose, SIGNAL(clicked()), this, SLOT(slotClose()));
connect(lstSessions, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(slotSessionDoubleClicked(QListWidgetItem*)));
}
void KisSessionManagerDialog::updateSessionList() {
KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer();
lstSessions->clear();
Q_FOREACH(KisSessionResource *session, server->resources()) {
lstSessions->addItem(session->name());
}
}
void KisSessionManagerDialog::slotNewSession()
{
QString name = QInputDialog::getText(this,
i18n("Create session"),
i18n("Session name:"), QLineEdit::Normal
);
if (name.isNull() || name.isEmpty()) return;
auto *session = new KisSessionResource(QString());
KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer();
QString saveLocation = server->saveLocation();
QFileInfo fileInfo(saveLocation + name + session->defaultFileExtension());
int i = 1;
while (fileInfo.exists()) {
fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + session->defaultFileExtension());
i++;
}
session->setFilename(fileInfo.filePath());
session->setName(name);
session->storeCurrentWindows();
server->addResource(session);
updateSessionList();
}
void KisSessionManagerDialog::slotRenameSession()
{
QString name = QInputDialog::getText(this,
i18n("Rename session"),
i18n("New name:"), QLineEdit::Normal
);
if (name.isNull() || name.isEmpty()) return;
KisSessionResource *session = getSelectedSession();
if (!session) return;
session->setName(name);
session->save();
updateSessionList();
}
void KisSessionManagerDialog::slotSessionDoubleClicked(QListWidgetItem* /*item*/)
{
slotSwitchSession();
slotClose();
}
void KisSessionManagerDialog::slotSwitchSession()
{
KisSessionResource *session = getSelectedSession();
if (session) {
bool closed = KisPart::instance()->closeSession(true);
if (closed) {
session->restore();
}
}
}
KisSessionResource *KisSessionManagerDialog::getSelectedSession() const
{
QListWidgetItem *item = lstSessions->currentItem();
if (item) {
KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer();
return server->resourceByName(item->text());
}
return nullptr;
}
void KisSessionManagerDialog::slotDeleteSession()
{
KisSessionResource *session = getSelectedSession();
if (!session) return;
if (QMessageBox::warning(this,
i18nc("@title:window", "Krita"),
QString(i18n("Permanently delete session %1?", session->name())),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
const QString filename = session->filename();
KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer();
server->removeResourceFromServer(session);
QFile file(filename);
file.remove();
updateSessionList();
}
}
void KisSessionManagerDialog::slotClose()
{
hide();
}
diff --git a/libs/ui/input/wintab/kis_tablet_support_win_p.h b/libs/ui/input/wintab/kis_tablet_support_win_p.h
index fb55f35398..64a8da3ea1 100644
--- a/libs/ui/input/wintab/kis_tablet_support_win_p.h
+++ b/libs/ui/input/wintab/kis_tablet_support_win_p.h
@@ -1,146 +1,146 @@
/*
* Copyright (C) 2015 The Qt Company Ltd.
* Contact: http://www.qt.io/licensing/
* Copyright (C) 2015 Michael Abrahms
*
* 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 3 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_TABLET_SUPPORT_WIN_P_H
#define KIS_TABLET_SUPPORT_WIN_P_H
-#include
-#include
+#include
+#include
#include
#include
#include "wintab.h"
QT_BEGIN_NAMESPACE
class QDebug;
class QWindow;
class QRect;
class QWidget;
struct QWindowsWinTab32DLL
{
QWindowsWinTab32DLL() : wTOpen(0), wTClose(0), wTInfo(0), wTEnable(0), wTOverlap(0), wTPacketsGet(0), wTGet(0),
wTQueueSizeGet(0), wTQueueSizeSet(0) {}
bool init();
typedef HCTX (API *PtrWTOpen)(HWND, LPLOGCONTEXT, BOOL);
typedef BOOL (API *PtrWTClose)(HCTX);
typedef UINT (API *PtrWTInfo)(UINT, UINT, LPVOID);
typedef BOOL (API *PtrWTEnable)(HCTX, BOOL);
typedef BOOL (API *PtrWTOverlap)(HCTX, BOOL);
typedef int (API *PtrWTPacketsGet)(HCTX, int, LPVOID);
typedef BOOL (API *PtrWTGet)(HCTX, LPLOGCONTEXT);
typedef int (API *PtrWTQueueSizeGet)(HCTX);
typedef BOOL (API *PtrWTQueueSizeSet)(HCTX, int);
PtrWTOpen wTOpen;
PtrWTClose wTClose;
PtrWTInfo wTInfo;
PtrWTEnable wTEnable;
PtrWTOverlap wTOverlap;
PtrWTPacketsGet wTPacketsGet;
PtrWTGet wTGet;
PtrWTQueueSizeGet wTQueueSizeGet;
PtrWTQueueSizeSet wTQueueSizeSet;
};
struct QWindowsTabletDeviceData
{
QWindowsTabletDeviceData() : minPressure(0), maxPressure(0), minTanPressure(0),
maxTanPressure(0), minX(0), maxX(0), minY(0), maxY(0), minZ(0), maxZ(0),
uniqueId(0), currentDevice(0), currentPointerType(0) {}
QPointF scaleCoordinates(int coordX, int coordY,const QRect &targetArea) const;
qreal scalePressure(qreal p) const { return p / qreal(maxPressure - minPressure); }
qreal scaleTangentialPressure(qreal p) const { return p / qreal(maxTanPressure - minTanPressure); }
int minPressure;
int maxPressure;
int minTanPressure;
int maxTanPressure;
int minX, maxX, minY, maxY, minZ, maxZ;
qint64 uniqueId;
int currentDevice;
int currentPointerType;
QRect virtualDesktopArea;
// Added by Krita
QMap buttonsMap;
};
QDebug operator<<(QDebug d, const QWindowsTabletDeviceData &t);
class QWindowsTabletSupport
{
Q_DISABLE_COPY(QWindowsTabletSupport)
explicit QWindowsTabletSupport(HWND window, HCTX context);
public:
~QWindowsTabletSupport();
static QWindowsTabletSupport *create();
void notifyActivate();
QString description() const;
bool translateTabletProximityEvent(WPARAM wParam, LPARAM lParam);
bool translateTabletPacketEvent();
int absoluteRange() const { return m_absoluteRange; }
void setAbsoluteRange(int a) { m_absoluteRange = a; }
void tabletUpdateCursor(const int pkCursor);
static QWindowsWinTab32DLL m_winTab32DLL;
private:
unsigned options() const;
QWindowsTabletDeviceData tabletInit(const quint64 uniqueId, const UINT cursorType) const;
const HWND m_window;
const HCTX m_context;
int m_absoluteRange;
bool m_tiltSupport;
QVector m_devices;
int m_currentDevice;
QWidget *targetWidget{0};
/**
* This is an inelegant solution to record pen / eraser switches.
* On the Surface Pro 3 we are only notified of cursor changes at the last minute.
* The recommended way to handle switches is WT_CSRCHANGE, but that doesn't work
* unless we save packet ID information, and we cannot change the structure of the
* PACKETDATA due to Qt restrictions.
*
* Furthermore, WT_CSRCHANGE only ever appears *after* we receive the packet.
*/
UINT currentPkCursor{0};
bool isSurfacePro3{false}; //< Only enable this on SP3 or other devices with the same issue.
};
QT_END_NAMESPACE
#endif // KIS_TABLET_SUPPORT_WIN_P_H
diff --git a/libs/ui/input/wintab/qxcbconnection_xi2.h b/libs/ui/input/wintab/qxcbconnection_xi2.h
index 8a1d21c1a5..32a13f0763 100644
--- a/libs/ui/input/wintab/qxcbconnection_xi2.h
+++ b/libs/ui/input/wintab/qxcbconnection_xi2.h
@@ -1,457 +1,457 @@
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "kis_debug.h"
#include
#include
#include
#include
#include
#include
#include
-#include
+#include
#include
#define XCB_USE_XINPUT2 1
#define XCB_USE_XLIB 1
// This is needed to make Qt compile together with XKB. xkb.h is using a variable
// which is called 'explicit', this is a reserved keyword in c++
#ifndef QT_NO_XKB
#define explicit dont_use_cxx_explicit
//#include
#undef explicit
#endif
#ifndef QT_NO_TABLETEVENT
#include
#endif
#if XCB_USE_XINPUT2
#include
#include