1 /***********************************************************************
2  *
3  * Copyright (C) 2012, 2013, 2014, 2016, 2018 Graeme Gott <graeme@gottcode.org>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  ***********************************************************************/
19 
20 #include "symbols_model.h"
21 
22 #include <QApplication>
23 #include <QBuffer>
24 #include <QDataStream>
25 #include <QFile>
26 #include <QFileInfo>
27 #include <QPalette>
28 
29 #include <climits>
30 
31 //-----------------------------------------------------------------------------
32 
operator >>(QDataStream & stream,SymbolsModel::Filter::Range & range)33 QDataStream& operator>>(QDataStream& stream, SymbolsModel::Filter::Range& range)
34 {
35 	stream >> range.start >> range.end;
36 	return stream;
37 }
38 
operator >>(QDataStream & stream,SymbolsModel::Filter & filter)39 QDataStream& operator>>(QDataStream& stream, SymbolsModel::Filter& filter)
40 {
41 	stream >> filter.name >> filter.size >> filter.ranges;
42 	return stream;
43 }
44 
45 //-----------------------------------------------------------------------------
46 
47 QString SymbolsModel::m_path;
48 
49 //-----------------------------------------------------------------------------
50 
SymbolsModel(QObject * parent)51 SymbolsModel::SymbolsModel(QObject* parent) :
52 	QAbstractItemModel(parent)
53 {
54 	QFile file(m_path);
55 	if (!file.open(QFile::ReadOnly)) {
56 		return;
57 	}
58 	QByteArray data = qUncompress(file.readAll());
59 	file.close();
60 
61 	QBuffer buffer(&data);
62 	if (!buffer.open(QBuffer::ReadOnly)) {
63 		return;
64 	}
65 
66 	QDataStream stream(&buffer);
67 	stream.setVersion(QDataStream::Qt_5_2);
68 	stream >> m_names;
69 	stream >> m_groups;
70 	buffer.close();
71 }
72 
73 //-----------------------------------------------------------------------------
74 
filters(int group) const75 QStringList SymbolsModel::filters(int group) const
76 {
77 	// Find group
78 	QStringList names;
79 	if ((group < 0) || (group >= m_groups.count())) {
80 		return names;
81 	}
82 	const FilterGroup& filters = m_groups.at(group);
83 
84 	// List names of all filters in group
85 	for (const Filter& filter : filters) {
86 		names += QLatin1String(filter.name);
87 	}
88 	return names;
89 }
90 
91 //-----------------------------------------------------------------------------
92 
filterGroups() const93 QStringList SymbolsModel::filterGroups() const
94 {
95 	return QStringList() << tr("Blocks") << tr("Scripts");
96 }
97 
98 //-----------------------------------------------------------------------------
99 
setFilter(int group,int index)100 void SymbolsModel::setFilter(int group, int index)
101 {
102 	// Find filter
103 	if ((group < 0) || (group >= m_groups.count())) {
104 		return;
105 	}
106 	const FilterGroup& filters = m_groups.at(group);
107 	if ((index < 0) || (index >= filters.count())) {
108 		return;
109 	}
110 	const Filter& filter = filters.at(index);
111 
112 	// Clear list of symbols
113 	beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
114 	m_symbols.resize(0);
115 	endRemoveRows();
116 
117 	// Allocate space for symbols
118 	int padding = filter.size % 16;
119 	padding = padding ? (16 - padding) : 0;
120 	int size = filter.size + padding;
121 	if (m_symbols.capacity() < size) {
122 		m_symbols.reserve(size);
123 	}
124 
125 	// Create list of symbols
126 	beginInsertRows(QModelIndex(), 0, (size / 16) - 1);
127 	for (int i = 0, count = filter.ranges.count(); i < count; ++i) {
128 		const Filter::Range& range = filter.ranges.at(i);
129 		for (quint32 code = range.start; code <= range.end; ++code) {
130 			m_symbols.append(code);
131 		}
132 	}
133 
134 	// Pad list of symbols to be multiple of 16
135 	for (int i = 0; i < padding; ++i) {
136 		m_symbols.append(UINT_MAX);
137 	}
138 	endInsertRows();
139 }
140 
141 //-----------------------------------------------------------------------------
142 
symbolFilter(int group,quint32 unicode) const143 int SymbolsModel::symbolFilter(int group, quint32 unicode) const
144 {
145 	// Find group
146 	int index = -1;
147 	if ((group < 0) || (group >= m_groups.count())) {
148 		return index;
149 	}
150 	const FilterGroup& filters = m_groups.at(group);
151 
152 	// Check for filter whose ranges contain symbol
153 	for (int i = 0, count = filters.count(); i < count; ++i) {
154 		const Filter& filter = filters.at(i);
155 		for (const Filter::Range& range : filter.ranges) {
156 			if ((range.start <= unicode) && (range.end >= unicode)) {
157 				index = i;
158 				break;
159 			}
160 		}
161 	}
162 	return index;
163 }
164 
165 //-----------------------------------------------------------------------------
166 
symbolName(quint32 unicode) const167 QString SymbolsModel::symbolName(quint32 unicode) const
168 {
169 	if ((unicode >= 0x3400 && unicode <= 0x4DBF)
170 			|| (unicode >= 0x4E00 && unicode <= 0x9FFF)
171 			|| (unicode >= 0x20000 && unicode <= 0x2A6DF)
172 			|| (unicode >= 0x2A700 && unicode <= 0x2B81F)) {
173 		return QLatin1String("CJK UNIFIED IDEOGRAPH-") + QString::number(unicode, 16).toUpper();
174 	} else if (unicode >= 0xAC00 && unicode <= 0xD7AF) {
175 		// Hangul character name algorithm from section 3.12 of Unicode standard
176 		static const int SBase = 0xAC00;
177 		static const int LCount = 19;
178 		static const int VCount = 21;
179 		static const int TCount = 28;
180 		static const int NCount = (VCount * TCount);
181 		static const int SCount = (LCount * NCount);
182 
183 		static const char JAMO_L_TABLE[][4] =
184 		{
185 			"G", "GG", "N", "D", "DD", "R", "M", "B", "BB",
186 			"S", "SS", "", "J", "JJ", "C", "K", "T", "P", "H"
187 		};
188 
189 		static const char JAMO_V_TABLE[][4] =
190 		{
191 			"A", "AE", "YA", "YAE", "EO", "E", "YEO", "YE", "O",
192 			"WA", "WAE", "OE", "YO", "U", "WEO", "WE", "WI",
193 			"YU", "EU", "YI", "I"
194 		};
195 
196 		static const char JAMO_T_TABLE[][4] =
197 		{
198 			"", "G", "GG", "GS", "N", "NJ", "NH", "D", "L", "LG", "LM",
199 			"LB", "LS", "LT", "LP", "LH", "M", "B", "BS",
200 			"S", "SS", "NG", "J", "C", "K", "T", "P", "H"
201 		};
202 
203 		int SIndex = unicode - SBase;
204 		if (SIndex < 0 || SIndex >= SCount) {
205 			return QString();
206 		}
207 
208 		int LIndex = SIndex / NCount;
209 		int VIndex = (SIndex % NCount) / TCount;
210 		int TIndex = SIndex % TCount;
211 
212 		return QLatin1String("HANGUL SYLLABLE ") + QLatin1String(JAMO_L_TABLE[LIndex]) +
213 				QLatin1String(JAMO_V_TABLE[VIndex]) + QLatin1String(JAMO_T_TABLE[TIndex]);
214 	} else {
215 		return QLatin1String(m_names[unicode]);
216 	}
217 }
218 
219 //-----------------------------------------------------------------------------
220 
columnCount(const QModelIndex & parent) const221 int SymbolsModel::columnCount(const QModelIndex& parent) const
222 {
223 	return !parent.isValid() ? 16 : 0;
224 }
225 
226 //-----------------------------------------------------------------------------
227 
data(const QModelIndex & index,int role) const228 QVariant SymbolsModel::data(const QModelIndex& index, int role) const
229 {
230 	if (!index.isValid()) {
231 		return QVariant();
232 	}
233 
234 	quint32 unicode = index.internalId();
235 	bool printable = QChar(unicode).isPrint();
236 	switch (role) {
237 	case Qt::BackgroundRole:
238 		return printable ? QVariant() : QApplication::palette().button();
239 	case Qt::DisplayRole:
240 		return printable ? QString::fromUcs4(&unicode, 1) : QString();
241 	case Qt::TextAlignmentRole:
242 		return Qt::AlignCenter;
243 	default:
244 		return QVariant();
245 	}
246 }
247 
248 //-----------------------------------------------------------------------------
249 
flags(const QModelIndex & index) const250 Qt::ItemFlags SymbolsModel::flags(const QModelIndex& index) const
251 {
252 	if (!index.isValid() || (index.internalId() > 0x10FFFF)) {
253 		return Qt::NoItemFlags;
254 	}
255 
256 	return Qt::ItemIsSelectable|Qt::ItemIsEnabled;
257 }
258 
259 //-----------------------------------------------------------------------------
260 
index(quint32 unicode) const261 QModelIndex SymbolsModel::index(quint32 unicode) const
262 {
263 	int index = m_symbols.indexOf(unicode);
264 	if (index != -1) {
265 		return createIndex(index / 16, index % 16, unicode);
266 	} else {
267 		return QModelIndex();
268 	}
269 }
270 
271 //-----------------------------------------------------------------------------
272 
index(int row,int column,const QModelIndex & parent) const273 QModelIndex SymbolsModel::index(int row, int column, const QModelIndex& parent) const
274 {
275 	int pos = (row * 16) + column;
276 	return (!parent.isValid() && (pos < m_symbols.count())) ? createIndex(row, column, m_symbols.at(pos)) : QModelIndex();
277 }
278 
279 //-----------------------------------------------------------------------------
280 
parent(const QModelIndex &) const281 QModelIndex SymbolsModel::parent(const QModelIndex&) const
282 {
283 	return QModelIndex();
284 }
285 
286 //-----------------------------------------------------------------------------
287 
rowCount(const QModelIndex & parent) const288 int SymbolsModel::rowCount(const QModelIndex& parent) const
289 {
290 	return !parent.isValid() ? (m_symbols.count() / 16) : 0;
291 }
292 
293 //-----------------------------------------------------------------------------
294 
setData(const QStringList & datadirs)295 void SymbolsModel::setData(const QStringList& datadirs)
296 {
297 	for (const QString& path : datadirs) {
298 		QFileInfo info(path + "/symbols1000.dat");
299 		if (info.exists()) {
300 			m_path = info.absoluteFilePath();
301 			break;
302 		}
303 	}
304 }
305 
306 //-----------------------------------------------------------------------------
307