1 /*
2    Drawpile - a collaborative drawing program.
3 
4    Copyright (C) 2007-2019 Calle Laakkonen
5 
6    Drawpile is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation, either version 3 of the License, or
9    (at your option) any later version.
10 
11    Drawpile is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with Drawpile.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "palette.h"
21 #include "../libshared/util/paths.h"
22 
23 #include <QDebug>
24 #include <QVariant>
25 #include <QColor>
26 #include <QFile>
27 #include <QFileInfo>
28 #include <QTextStream>
29 #include <QRegularExpression>
30 
Palette(QObject * parent)31 Palette::Palette(QObject *parent) : Palette(QString(), QString(), false, parent) { }
Palette(const QString & name,QObject * parent)32 Palette::Palette(const QString &name, QObject *parent) : Palette(name, QString(), false, parent) { }
33 
Palette(const QString & name,const QString & filename,bool readonly,QObject * parent)34 Palette::Palette(const QString& name, const QString& filename, bool readonly, QObject *parent)
35 	: QObject(parent), _name(name), _oldname(name), _filename(filename), _columns(8), _modified(false), _readonly(readonly), _writeprotect(false)
36 {
37 }
38 
39 /**
40  * Load a palette from a GIMP palette file.
41  *
42  * The file format is:
43  *
44  *     GIMP Palette
45  *     *HEADER FIELDS*
46  *     # one or more comment
47  *     r g b	name
48  *     ...
49  *
50  * @param filename palette file name
51  * @param writeprotected is the source file read only
52  */
fromFile(const QFileInfo & file,bool readonly,QObject * parent)53 Palette *Palette::fromFile(const QFileInfo& file, bool readonly, QObject *parent)
54 {
55 	QFile palfile(file.absoluteFilePath());
56 	if (!palfile.open(QIODevice::ReadOnly | QIODevice::Text))
57 		return nullptr;
58 
59 	QTextStream in(&palfile);
60 	if(in.readLine() != "GIMP Palette")
61 		return nullptr;
62 
63 	Palette *pal = new Palette(file.baseName(), file.absoluteFilePath(), !file.isWritable() | readonly, parent);
64 
65 	const QRegularExpression colorRe("^(\\d+)\\s+(\\d+)\\s+(\\d+)\\s*(.+)?$");
66 
67 	do {
68 		QString line = in.readLine().trimmed();
69 		if(line.isEmpty() || line.at(0) == '#') {
70 			// ignore comments and empty lines
71 
72 		} else if(line.startsWith("Name:")) {
73 			pal->_name = line.mid(5).trimmed();
74 
75 		} else if(line.startsWith("Columns:")) {
76 			bool ok;
77 			int cols = line.mid(9).trimmed().toInt(&ok);
78 			if(ok && cols>0)
79 				pal->_columns = cols;
80 
81 		} else {
82 			QRegularExpressionMatch m = colorRe.match(line);
83 			if(m.hasMatch()) {
84 				pal->_colors.append(PaletteColor(
85 					QColor(
86 						m.captured(1).toInt(),
87 						m.captured(2).toInt(),
88 						m.captured(3).toInt()
89 					),
90 					m.captured(4)
91 					)
92 				);
93 
94 			} else {
95 				qWarning() << "unhandled line" << line << "in" << file.fileName();
96 			}
97 		}
98 	} while(!in.atEnd());
99 
100 	// Palettes loaded from file are write-protected by default
101 	pal->_writeprotect = true;
102 
103 	return pal;
104 }
105 
copy(const Palette * pal,const QString & newname,QObject * parent)106 Palette *Palette::copy(const Palette *pal, const QString &newname, QObject *parent)
107 {
108 	Q_ASSERT(pal);
109 	Palette *p = new Palette(newname, parent);
110 	p->_columns = pal->_columns;
111 	p->_colors = pal->_colors;
112 	p->_modified = true;
113 	return p;
114 }
115 
116 /**
117  * @param filename palette file name
118  */
save(const QString & filename)119 bool Palette::save(const QString& filename)
120 {
121 	return exportPalette(filename);
122 }
123 
exportPalette(const QString & filename,QString * errorString)124 bool Palette::exportPalette(const QString &filename, QString *errorString)
125 {
126 	QFile data(filename);
127 	if (data.open(QFile::WriteOnly | QFile::Truncate)) {
128 		QTextStream out(&data);
129 		out << "GIMP Palette\n";
130 		out << "Name: " << _name << "\n";
131 		out << "Columns: " << _columns << "\n";
132 		out << "#\n";
133 		for(const PaletteColor &c : _colors) {
134 			out << c.color.red() << ' ' << c.color.green() << ' ' << c.color.blue() << '\t' << c.name << '\n';
135 		}
136 		return true;
137 
138 	} else {
139 		if(errorString)
140 			*errorString = data.errorString();
141 		qWarning() << filename << data.errorString();
142 		return false;
143 	}
144 }
145 
save()146 bool Palette::save()
147 {
148 	if(_readonly)
149 		return false;
150 
151 	QString oldpath;
152 	if(_name != _oldname) {
153 		// Name has changed: we need to delete the old palette
154 		oldpath = _filename;
155 		_filename = QString();
156 	}
157 
158 	if(_filename.isEmpty()) {
159 		// No filename set? Create it from the palette name
160 		_filename = utils::paths::writablePath("palettes/", _name + ".gpl");
161 	}
162 
163 	bool ok = save(_filename);
164 	if(ok) {
165 		_oldname = _name;
166 		_modified = false;
167 
168 		if(!oldpath.isEmpty() && oldpath != _filename)
169 			QFile(oldpath).remove();
170 	}
171 
172 	return ok;
173 }
174 
deleteFile()175 bool Palette::deleteFile()
176 {
177 	if(_filename.isEmpty() || _readonly)
178 		return false;
179 	return QFile(_filename).remove();
180 }
181 
182 /**
183  * Change the palette name.
184  * The filename is set as the name + extension ".gpl"
185  * @param name new palette name
186  */
setName(const QString & name)187 void Palette::setName(const QString& name)
188 {
189 	if(_name != name) {
190 		_name = name;
191 		_modified = true;
192 		emit nameChanged();
193 	}
194 }
195 
setColumns(int columns)196 void Palette::setColumns(int columns)
197 {
198 	if(_columns != columns) {
199 		_columns = columns;
200 		_modified = true;
201 		emit columnsChanged();
202 	}
203 }
204 
205 /**
206  * Size of the palette is increased by one. The new color is
207  * inserted before the index. If index == count(), the color is
208  * added to the end of the palette.
209  * @param index color index
210  * @param color color
211  * @pre 0 <= index <= count()
212 */
setColor(int index,const PaletteColor & color)213 void Palette::setColor(int index, const PaletteColor& color)
214 {
215 	if(_readonly)
216 		return;
217 
218 	_colors[index] = color;
219 	_modified = true;
220 	emit colorsChanged();
221 }
222 
insertColor(int index,const QColor & color,const QString & name)223 void Palette::insertColor(int index, const QColor& color, const QString &name)
224 {
225 	if(_readonly)
226 		return;
227 
228 	_colors.insert(index, PaletteColor(color, name));
229 	_modified = true;
230 	emit colorsChanged();
231 }
232 
appendColor(const QColor & color,const QString & name)233 void Palette::appendColor(const QColor &color, const QString &name)
234 {
235 	if(_readonly)
236 		return;
237 
238 	_colors.append(PaletteColor(color, name));
239 	_modified = true;
240 	emit colorsChanged();
241 }
242 
243 /**
244  * Size of the palette is decreased by one.
245  * @param index color index
246  * @pre 0 <= index < count()
247 */
removeColor(int index)248 void Palette::removeColor(int index)
249 {
250 	if(_readonly)
251 		return;
252 
253 	_colors.removeAt(index);
254 	_modified = true;
255 	emit colorsChanged();
256 }
257 
258