forked from openkylin/kolourpaint
519 lines
14 KiB
C++
519 lines
14 KiB
C++
|
|
// REFACT0R: Remote open/save file logic is duplicated in kpDocument.
|
|
// HITODO: Test when remote file support in KDE 4 stabilizes
|
|
|
|
/* This file is part of the KDE libraries
|
|
Copyright (C) 1999 Waldo Bastian (bastian@kde.org)
|
|
Copyright (C) 2007 Clarence Dang (dang@kde.org)
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License.
|
|
|
|
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.
|
|
*/
|
|
//-----------------------------------------------------------------------------
|
|
// KDE color collection
|
|
|
|
#define DEBUG_KP_COLOR_COLLECTION 0
|
|
|
|
#include "kpColorCollection.h"
|
|
|
|
#include "kpUrlFormatter.h"
|
|
|
|
#include <KJobWidgets>
|
|
#include <KIO/StoredTransferJob>
|
|
#include <KIO/FileCopyJob>
|
|
#include <KLocalizedString>
|
|
#include <KMessageBox>
|
|
#include "kpLogCategories.h"
|
|
#include <KStringHandler>
|
|
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QSaveFile>
|
|
#include <QStandardPaths>
|
|
#include <QTemporaryFile>
|
|
#include <QTextStream>
|
|
#include <QUrl>
|
|
|
|
struct ColorNode
|
|
{
|
|
ColorNode(const QColor &c, const QString &n)
|
|
: color(c), name(n) {}
|
|
|
|
QColor color;
|
|
QString name;
|
|
};
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
Q_LOGGING_CATEGORY(kpLogColorCollection, "kp.colorCollection")
|
|
|
|
//BEGIN kpColorCollectionPrivate
|
|
class kpColorCollectionPrivate
|
|
{
|
|
public:
|
|
kpColorCollectionPrivate();
|
|
kpColorCollectionPrivate(const kpColorCollectionPrivate&);
|
|
|
|
QList<ColorNode> colorList;
|
|
QString name;
|
|
QString desc;
|
|
kpColorCollection::Editable editable;
|
|
};
|
|
|
|
kpColorCollectionPrivate::kpColorCollectionPrivate()
|
|
: editable(kpColorCollection::Yes)
|
|
{
|
|
}
|
|
|
|
kpColorCollectionPrivate::kpColorCollectionPrivate(const kpColorCollectionPrivate& p)
|
|
: colorList(p.colorList), name(p.name), desc(p.desc), editable(p.editable)
|
|
{
|
|
}
|
|
//END kpColorCollectionPrivate
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
QStringList
|
|
kpColorCollection::installedCollections()
|
|
{
|
|
QStringList paletteList;
|
|
|
|
QStringList paths = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("colors"),
|
|
QStandardPaths::LocateDirectory);
|
|
for (const auto &path : paths) {
|
|
paletteList.append(QDir(path).entryList(QStringList(), QDir::Files));
|
|
}
|
|
|
|
return paletteList;
|
|
}
|
|
|
|
kpColorCollection::kpColorCollection()
|
|
{
|
|
d = new kpColorCollectionPrivate();
|
|
}
|
|
|
|
kpColorCollection::kpColorCollection(const kpColorCollection &p)
|
|
{
|
|
d = new kpColorCollectionPrivate(*p.d);
|
|
}
|
|
|
|
kpColorCollection::~kpColorCollection()
|
|
{
|
|
// Need auto-save?
|
|
delete d;
|
|
}
|
|
|
|
static void CouldNotOpenDialog (const QUrl &url, QWidget *parent)
|
|
{
|
|
KMessageBox::sorry (parent,
|
|
i18n ("Could not open color palette \"%1\".",
|
|
kpUrlFormatter::PrettyFilename (url)));
|
|
}
|
|
|
|
// TODO: Set d->editable?
|
|
bool
|
|
kpColorCollection::open(const QUrl &url, QWidget *parent)
|
|
{
|
|
if (url.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
KIO::StoredTransferJob *job = KIO::storedGet (url);
|
|
KJobWidgets::setWindow (job, parent);
|
|
|
|
if (!job->exec ())
|
|
{
|
|
#if DEBUG_KP_COLOR_COLLECTION
|
|
qCDebug(kpLogColorCollection) << "\tcould not download";
|
|
#endif
|
|
::CouldNotOpenDialog (url, parent);
|
|
return false;
|
|
}
|
|
|
|
const QByteArray &data = job->data();
|
|
QTextStream stream(data);
|
|
|
|
// Read first line
|
|
// Expected "GIMP Palette" or "KDE RGB Palette" or "KDE RGBA Palette"
|
|
QString line = stream.readLine();
|
|
if (line.indexOf(QLatin1String(" Palette")) == -1)
|
|
{
|
|
KMessageBox::sorry (parent,
|
|
i18n ("Could not open color palette \"%1\" - unsupported format.\n"
|
|
"The file may be corrupt.",
|
|
kpUrlFormatter::PrettyFilename (url)));
|
|
return false;
|
|
}
|
|
|
|
bool hasAlpha = line == QLatin1String("KDE RGBA Palette"); // new format includes alpha
|
|
|
|
QList <ColorNode> newColorList;
|
|
QString newDesc;
|
|
|
|
while( !stream.atEnd() )
|
|
{
|
|
line = stream.readLine();
|
|
if ( !line.isEmpty() && (line[0] == '#') )
|
|
{
|
|
// This is a comment line
|
|
line = line.mid(1); // Strip '#'
|
|
line = line.trimmed(); // Strip remaining white space..
|
|
if (!line.isEmpty())
|
|
{
|
|
newDesc += line+'\n'; // Add comment to description
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This is a color line, hopefully
|
|
line = line.trimmed();
|
|
if (line.isEmpty()) continue;
|
|
int r, g, b, a = 255;
|
|
int pos = 0;
|
|
bool ok = false;
|
|
|
|
if ( hasAlpha )
|
|
ok = (sscanf(line.toLatin1(), "%d %d %d %d%n", &r, &g, &b, &a, &pos) >= 4);
|
|
else
|
|
ok = (sscanf(line.toLatin1(), "%d %d %d%n", &r, &g, &b, &pos) >= 3);
|
|
|
|
if ( ok )
|
|
{
|
|
r = qBound(0, r, 255);
|
|
g = qBound(0, g, 255);
|
|
b = qBound(0, b, 255);
|
|
a = qBound(0, a, 255);
|
|
QString name = line.mid(pos).trimmed();
|
|
newColorList.append(ColorNode(QColor(r, g, b, a), name));
|
|
}
|
|
}
|
|
}
|
|
|
|
d->colorList = newColorList;
|
|
d->name.clear ();
|
|
d->desc = newDesc;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void CouldNotOpenKDEDialog (const QString &name, QWidget *parent)
|
|
{
|
|
KMessageBox::sorry (parent,
|
|
i18n ("Could not open KDE color palette \"%1\".", name));
|
|
}
|
|
|
|
bool
|
|
kpColorCollection::openKDE(const QString &name, QWidget *parent)
|
|
{
|
|
#if DEBUG_KP_COLOR_COLLECTION
|
|
qCDebug(kpLogColorCollection) << "name=" << name;
|
|
#endif
|
|
|
|
if (name.isEmpty())
|
|
{
|
|
#if DEBUG_KP_COLOR_COLLECTION
|
|
qCDebug(kpLogColorCollection) << "name.isEmpty";
|
|
#endif
|
|
::CouldNotOpenKDEDialog (name, parent);
|
|
return false;
|
|
}
|
|
|
|
QString filename = QStandardPaths::locate(QStandardPaths::GenericConfigLocation,
|
|
"colors/" + name);
|
|
if (filename.isEmpty())
|
|
{
|
|
#if DEBUG_KP_COLOR_COLLECTION
|
|
qCDebug(kpLogColorCollection) << "could not find file";
|
|
#endif
|
|
::CouldNotOpenKDEDialog (name, parent);
|
|
return false;
|
|
}
|
|
|
|
// (this will pop up an error dialog on failure)
|
|
if (!open (QUrl::fromLocalFile (filename), parent))
|
|
{
|
|
#if DEBUG_KP_COLOR_COLLECTION
|
|
qCDebug(kpLogColorCollection) << "could not open";
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
d->name = name;
|
|
#if DEBUG_KP_COLOR_COLLECTION
|
|
qCDebug(kpLogColorCollection) << "opened";
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
static void CouldNotSaveDialog (const QUrl &url, QWidget *parent)
|
|
{
|
|
// TODO: use file.errorString()
|
|
KMessageBox::error (parent,
|
|
i18n ("Could not save color palette as \"%1\".",
|
|
kpUrlFormatter::PrettyFilename (url)));
|
|
}
|
|
|
|
static void SaveToFile (kpColorCollectionPrivate *d, QIODevice *device)
|
|
{
|
|
// HITODO: QTextStream can fail but does not report errors.
|
|
// Bug in KColorCollection too.
|
|
QTextStream str (device);
|
|
|
|
QString description = d->desc.trimmed();
|
|
description = '#' + description.split('\n', Qt::KeepEmptyParts).join(QLatin1String("\n#"));
|
|
|
|
str << "KDE RGBA Palette\n";
|
|
str << description << "\n";
|
|
for (const auto &node : d->colorList)
|
|
{
|
|
// Added for KolourPaint.
|
|
if ( !node.color.isValid() )
|
|
continue;
|
|
|
|
int r, g, b, a;
|
|
node.color.getRgb(&r, &g, &b, &a);
|
|
str << r << " " << g << " " << b << " " << a << " " << node.name << "\n";
|
|
}
|
|
|
|
str.flush();
|
|
}
|
|
|
|
bool
|
|
kpColorCollection::saveAs(const QUrl &url, QWidget *parent) const
|
|
{
|
|
if (url.isLocalFile ())
|
|
{
|
|
const QString filename = url.toLocalFile ();
|
|
|
|
// sync: All failure exit paths _must_ call QSaveFile::cancelWriting() or
|
|
// else, the QSaveFile destructor will overwrite the file,
|
|
// <filename>, despite the failure.
|
|
QSaveFile atomicFileWriter (filename);
|
|
{
|
|
if (!atomicFileWriter.open (QIODevice::WriteOnly))
|
|
{
|
|
// We probably don't need this as <filename> has not been
|
|
// opened.
|
|
atomicFileWriter.cancelWriting ();
|
|
|
|
#if DEBUG_KP_COLOR_COLLECTION
|
|
qCDebug(kpLogColorCollection) << "\treturning false because could not open QSaveFile"
|
|
<< " error=" << atomicFileWriter.error ();
|
|
#endif
|
|
::CouldNotSaveDialog (url, parent);
|
|
return false;
|
|
}
|
|
|
|
// Write to local temporary file.
|
|
::SaveToFile (d, &atomicFileWriter);
|
|
|
|
// Atomically overwrite local file with the temporary file
|
|
// we saved to.
|
|
if (!atomicFileWriter.commit ())
|
|
{
|
|
atomicFileWriter.cancelWriting ();
|
|
|
|
#if DEBUG_KP_COLOR_COLLECTION
|
|
qCDebug(kpLogColorCollection) << "\tcould not close QSaveFile";
|
|
#endif
|
|
::CouldNotSaveDialog (url, parent);
|
|
return false;
|
|
}
|
|
} // sync QSaveFile.cancelWriting()
|
|
}
|
|
// Remote file?
|
|
else
|
|
{
|
|
// Create temporary file that is deleted when the variable goes
|
|
// out of scope.
|
|
QTemporaryFile tempFile;
|
|
if (!tempFile.open ())
|
|
{
|
|
#if DEBUG_KP_COLOR_COLLECTION
|
|
qCDebug(kpLogColorCollection) << "\treturning false because could not open tempFile";
|
|
#endif
|
|
::CouldNotSaveDialog (url, parent);
|
|
return false;
|
|
}
|
|
|
|
// Write to local temporary file.
|
|
::SaveToFile (d, &tempFile);
|
|
|
|
// Collect name of temporary file now, as QTemporaryFile::fileName()
|
|
// stops working after close() is called.
|
|
const QString tempFileName = tempFile.fileName ();
|
|
#if DEBUG_KP_COLOR_COLLECTION
|
|
qCDebug(kpLogColorCollection) << "\ttempFileName='" << tempFileName << "'";
|
|
#endif
|
|
Q_ASSERT (!tempFileName.isEmpty ());
|
|
|
|
tempFile.close ();
|
|
if (tempFile.error () != QFile::NoError)
|
|
{
|
|
#if DEBUG_KP_COLOR_COLLECTION
|
|
qCDebug(kpLogColorCollection) << "\treturning false because could not close";
|
|
#endif
|
|
::CouldNotSaveDialog (url, parent);
|
|
return false;
|
|
}
|
|
|
|
// Copy local temporary file to overwrite remote.
|
|
KIO::FileCopyJob *job = KIO::file_copy (QUrl::fromLocalFile (tempFileName),
|
|
url,
|
|
-1,
|
|
KIO::Overwrite);
|
|
KJobWidgets::setWindow (job, parent);
|
|
if (!job->exec ())
|
|
{
|
|
#if DEBUG_KP_COLOR_COLLECTION
|
|
qCDebug(kpLogColorCollection) << "\treturning false because could not upload";
|
|
#endif
|
|
::CouldNotSaveDialog (url, parent);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
d->name.clear ();
|
|
return true;
|
|
}
|
|
|
|
QString kpColorCollection::description() const
|
|
{
|
|
return d->desc;
|
|
}
|
|
|
|
void kpColorCollection::setDescription(const QString &desc)
|
|
{
|
|
d->desc = desc;
|
|
}
|
|
|
|
QString kpColorCollection::name() const
|
|
{
|
|
return d->name;
|
|
}
|
|
|
|
void kpColorCollection::setName(const QString &name)
|
|
{
|
|
d->name = name;
|
|
}
|
|
|
|
kpColorCollection::Editable kpColorCollection::editable() const
|
|
{
|
|
return d->editable;
|
|
}
|
|
|
|
void kpColorCollection::setEditable(Editable editable)
|
|
{
|
|
d->editable = editable;
|
|
}
|
|
|
|
int kpColorCollection::count() const
|
|
{
|
|
return (int) d->colorList.count();
|
|
}
|
|
|
|
void kpColorCollection::resize(int newCount)
|
|
{
|
|
if (newCount == count())
|
|
return;
|
|
else if (newCount < count())
|
|
{
|
|
d->colorList.erase(d->colorList.begin() + newCount, d->colorList.end());
|
|
}
|
|
else if (newCount > count())
|
|
{
|
|
while(newCount > count())
|
|
{
|
|
const int ret = addColor(QColor(), QString()/*color name*/);
|
|
Q_ASSERT(ret == count() - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
kpColorCollection&
|
|
kpColorCollection::operator=( const kpColorCollection &p)
|
|
{
|
|
if (&p == this) return *this;
|
|
d->colorList = p.d->colorList;
|
|
d->name = p.d->name;
|
|
d->desc = p.d->desc;
|
|
d->editable = p.d->editable;
|
|
return *this;
|
|
}
|
|
|
|
QColor
|
|
kpColorCollection::color(int index) const
|
|
{
|
|
if ((index < 0) || (index >= count()))
|
|
return {};
|
|
|
|
return d->colorList[index].color;
|
|
}
|
|
|
|
int
|
|
kpColorCollection::findColor(const QColor &color) const
|
|
{
|
|
for (int i = 0; i < d->colorList.size(); ++i)
|
|
{
|
|
if (d->colorList[i].color == color)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
QString
|
|
kpColorCollection::name(int index) const
|
|
{
|
|
if ((index < 0) || (index >= count()))
|
|
return {};
|
|
|
|
return d->colorList[index].name;
|
|
}
|
|
|
|
QString kpColorCollection::name(const QColor &color) const
|
|
{
|
|
return name(findColor(color));
|
|
}
|
|
|
|
int
|
|
kpColorCollection::addColor(const QColor &newColor, const QString &newColorName)
|
|
{
|
|
d->colorList.append(ColorNode(newColor, newColorName));
|
|
return count() - 1;
|
|
}
|
|
|
|
int
|
|
kpColorCollection::changeColor(int index,
|
|
const QColor &newColor,
|
|
const QString &newColorName)
|
|
{
|
|
if ((index < 0) || (index >= count()))
|
|
return -1;
|
|
|
|
ColorNode& node = d->colorList[index];
|
|
node.color = newColor;
|
|
node.name = newColorName;
|
|
|
|
return index;
|
|
}
|
|
|
|
int kpColorCollection::changeColor(const QColor &oldColor,
|
|
const QColor &newColor,
|
|
const QString &newColorName)
|
|
{
|
|
return changeColor( findColor(oldColor), newColor, newColorName);
|
|
}
|
|
|