1 /*
2    Drawpile - a collaborative drawing program.
3 
4    Copyright (C) 2014-2015 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 "palettelistmodel.h"
21 #include "palette.h"
22 #include "icon.h"
23 #include "../libshared/util/paths.h"
24 
25 #include <QDebug>
26 #include <QFileInfo>
27 #include <QDir>
28 
PaletteListModel(QObject * parent)29 PaletteListModel::PaletteListModel(QObject *parent) :
30 	QAbstractListModel(parent),
31 	_readonlyEmblem(icon::fromTheme("object-locked"))
32 {
33 }
34 
getSharedInstance()35 PaletteListModel *PaletteListModel::getSharedInstance()
36 {
37 	static PaletteListModel *instance;
38 	if(!instance) {
39 		instance = new PaletteListModel;
40 		instance->loadPalettes();
41 	}
42 	return instance;
43 }
44 
loadPalettes()45 void PaletteListModel::loadPalettes()
46 {
47 	QList<Palette*> palettes;
48 
49 	const QStringList datapaths = utils::paths::dataPaths();
50 	const QString writablepath = utils::paths::writablePath(QString());
51 	QSet<QString> palettefiles;
52 
53 	for(const QString &datapath : datapaths) {
54 		QFileInfoList pfiles = QDir(datapath + "/palettes").entryInfoList(
55 				QStringList("*.gpl"),
56 				QDir::Files|QDir::Readable
57 				);
58 
59 		for(const QFileInfo &pfile : pfiles) {
60 			if(!palettefiles.contains(pfile.fileName())) {
61 				palettefiles.insert(pfile.fileName());
62 
63 				// QFile::isWritable doesn't seem to work reliably on Windows.
64 				// As a workaround, mark all palettes outside our own writable directory
65 				// as read-only
66 
67 				Palette *pal = Palette::fromFile(pfile, datapath != writablepath, this);
68 				if(!pal)
69 					qWarning() << "Invalid palette:" << pfile.absoluteFilePath();
70 
71 				else
72 					palettes.append(pal);
73 			}
74 		}
75 	}
76 
77 	beginResetModel();
78 	_palettes = palettes;
79 	endResetModel();
80 }
81 
saveChanged()82 void PaletteListModel::saveChanged()
83 {
84 	for(Palette *pal : _palettes) {
85 		if(pal->isModified())
86 			pal->save();
87 	}
88 }
89 
rowCount(const QModelIndex & parent) const90 int PaletteListModel::rowCount(const QModelIndex &parent) const
91 {
92 	if(parent.isValid())
93 		return 0;
94 
95 	return _palettes.size();
96 }
97 
data(const QModelIndex & index,int role) const98 QVariant PaletteListModel::data(const QModelIndex &index, int role) const
99 {
100 	if(index.isValid() && index.row() >= 0 && index.row() < _palettes.size()) {
101 		switch(role) {
102 		case Qt::DisplayRole:
103 		case Qt::EditRole:
104 			return _palettes.at(index.row())->name();
105 		case Qt::DecorationRole:
106 			if(_palettes.at(index.row())->isReadonly())
107 				return _readonlyEmblem;
108 			break;
109 		}
110 	}
111 
112 	return QVariant();
113 }
114 
setData(const QModelIndex & index,const QVariant & value,int role)115 bool PaletteListModel::setData(const QModelIndex &index, const QVariant &value, int role)
116 {
117 	if(role==Qt::EditRole && index.isValid() && index.row() >= 0 && index.row() < _palettes.size()) {
118 		QString newname = value.toString().trimmed();
119 
120 		if(_palettes[index.row()]->isReadonly())
121 			return false;
122 
123 		// Name must be non-empty and unique
124 		if(newname.isEmpty() || !isUniqueName(newname, index.row()))
125 			return false;
126 
127 		_palettes[index.row()]->setName(value.toString());
128 		emit dataChanged(index, index);
129 		return true;
130 	}
131 	return false;
132 }
133 
flags(const QModelIndex & index) const134 Qt::ItemFlags PaletteListModel::flags(const QModelIndex &index) const
135 {
136 	Qt::ItemFlags f = Qt::NoItemFlags;
137 	if(index.isValid() && index.row() >= 0 && index.row() < _palettes.size()) {
138 		f = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
139 
140 		if(!_palettes.at(index.row())->isReadonly())
141 			f |= Qt::ItemIsEditable;
142 	}
143 
144 	return f;
145 }
146 
getPalette(int index)147 Palette *PaletteListModel::getPalette(int index)
148 {
149 	Q_ASSERT(index>=0 && index<_palettes.size());
150 	if(index>=0 && index<_palettes.size())
151 		return _palettes[index];
152 
153 	return nullptr;
154 }
155 
findPalette(const QString & name) const156 int PaletteListModel::findPalette(const QString &name) const
157 {
158 	for(int i=0;i<_palettes.size();++i)
159 		if(_palettes.at(i)->name() == name)
160 			return i;
161 	return -1;
162 }
163 
addNewPalette()164 void PaletteListModel::addNewPalette()
165 {
166 	QString name;
167 	bool nameOk = false;
168 
169 	// Autogenerate name
170 	for(int tries=0;tries<99;++tries) {
171 		name = tr("New palette");
172 		if(tries>0)
173 			name = name + " " + QString::number(tries);
174 
175 		if(isUniqueName(name)) {
176 			// name found
177 			nameOk = true;
178 			break;
179 		}
180 	}
181 
182 	if(!nameOk)
183 		return;
184 
185 	int pos = _palettes.size();
186 	beginInsertRows(QModelIndex(), pos, pos);
187 	_palettes.append(new Palette(name, this));
188 	endInsertRows();
189 }
190 
copyPalette(int index)191 bool PaletteListModel::copyPalette(int index)
192 {
193 	if(index<0 || index >= _palettes.size())
194 		return false;
195 
196 	const Palette *pal = _palettes.at(index);
197 
198 	QString basename = pal->name();
199 	while(basename.size()>0 && (basename.at(basename.size()-1).isDigit() || basename.at(basename.size()-1).isSpace()))
200 		basename.chop(1);
201 	basename.append(' ');
202 
203 	QString name;
204 	for(int tries=2;tries<99;++tries) {
205 		QString newname = basename + QString::number(tries);
206 
207 		if(isUniqueName(newname)) {
208 			name = newname;
209 			break;
210 		}
211 	}
212 
213 	if(name.isNull())
214 		return false;
215 
216 	Palette *copy = Palette::copy(pal, name, this);
217 	beginInsertRows(QModelIndex(), index, index);
218 	_palettes.insert(index, copy);
219 	endInsertRows();
220 
221 	return true;
222 }
223 
isUniqueName(const QString & name,int exclude) const224 bool PaletteListModel::isUniqueName(const QString &name, int exclude) const
225 {
226 	for(int i=0;i<_palettes.size();++i)
227 		if(i != exclude && _palettes.at(i)->name().compare(name, Qt::CaseInsensitive) == 0)
228 			return false;
229 	return true;
230 }
231 
removeRows(int row,int count,const QModelIndex & parent)232 bool PaletteListModel::removeRows(int row, int count, const QModelIndex &parent)
233 {
234 	if(parent.isValid() || row < 0 || row+count > _palettes.size())
235 		return false;
236 
237 	beginRemoveRows(parent, row, row+count-1);
238 
239 	while(count-->0) {
240 		Palette *pal = _palettes.takeAt(row);
241 		pal->deleteFile();
242 		delete pal;
243 	}
244 
245 	endRemoveRows();
246 	return true;
247 }
248