1 /****************************************************************************************
2 begin : Fri Aug 1 2003
3 copyright : (C) 2003 by Jeroen Wijnhout (Jeroen.Wijnhout@kdemail.net)
4 2006 - 2009 by Thomas Braun
5 2012 - 2019 by Michel Ludwig (michel.ludwig@kdemail.net)
6 ****************************************************************************************/
7
8 /***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17 /*
18 dani 2005-11-22
19 - add some new symbols
20 - rearranged source
21
22 tbraun 2006-07-01
23 - added tooltips which show the keys, copied from kfileiconview
24 - reorganized the hole thing, more flexible png loading, removing the old big code_array, more groups
25
26 tbraun 2007-06-04
27 - Send a warning in the logwidget if needed packages are not included for the command
28 tbraun 2007-06-13
29 - Added Most frequently used symbolview, including remembering icons upon restart, removing of least popular item and configurable max item count
30 */
31
32
33 #include "symbolview.h"
34
35 #include <QApplication>
36 #include <QMouseEvent>
37 #include <QPixmap>
38 #include <QPainter>
39 #include <QRegExp>
40 #include <QStringList>
41 #include <QTextDocument>
42
43 #include <KColorScheme>
44 #include <KConfig>
45 #include <KLocalizedString>
46
47 #include "kileconfig.h"
48 #include "kiledebug.h"
49 #include "kileinfo.h"
50 #include "../symbolviewclasses.h"
51 #include "utilities.h"
52
53 #define MFUS_GROUP "MostFrequentlyUsedSymbols"
54 #define MFUS_PREFIX "MFUS"
55
56
57 namespace KileWidget {
58
SymbolView(KileInfo * kileInfo,QWidget * parent,int type,const char * name)59 SymbolView::SymbolView(KileInfo *kileInfo, QWidget *parent, int type, const char *name)
60 : QListWidget(parent), m_ki(kileInfo)
61 {
62 setObjectName(name);
63 setViewMode(IconMode);
64 setGridSize(QSize(36, 36));
65 setSpacing(5);
66 setWordWrap(false);
67 setResizeMode(Adjust);
68 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
69 setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
70 setMovement(Static);
71 setSortingEnabled(false);
72 setFlow(LeftToRight);
73 setDragDropMode(NoDragDrop);
74 m_brush = KStatefulBrush(KColorScheme::View, KColorScheme::NormalText);
75 initPage(type);
76 }
77
~SymbolView()78 SymbolView::~SymbolView()
79 {
80 }
81
82 /* key format
83 from old symbols with package info
84 1%\textonequarter%%%{textcomp}%%/home/kdedev/.kde4/share/apps/kile/mathsymbols/misc-text/img072misc-text.png
85 from old symbols without package info
86 1%\oldstylenums{9}%%%%%/home/kdedev/.kde4/share/apps/kile/mathsymbols/misc-text/img070misc-text.png
87 new symbol
88 1%\neq%≠%[utf8x,,]{inputenc,ucs,}%[fleqn,]{amsmath,}%This command gives nice weather!%/home/kdedev/.kde4/share/apps/kile/mathsymbols/user/img002math.png
89 */
90
extract(const QString & key,int & refCnt)91 void SymbolView::extract(const QString& key, int& refCnt)
92 {
93 if (!key.isEmpty()) {
94 refCnt = key.section('%', 0, 0).toInt();
95 }
96 }
97
extractPackageString(const QString & string,QList<Package> & packages)98 void SymbolView::extractPackageString(const QString&string, QList<Package> &packages)
99 {
100 QRegExp rePkgs("(?:\\[(.*)\\])?\\{(.*)\\}");
101 QStringList args,pkgs;
102 Package pkg;
103
104 if(string.isEmpty()) {
105 return;
106 }
107
108 packages.clear();
109
110 if(rePkgs.exactMatch(string)) {
111 args = rePkgs.cap(1).split(',');
112 pkgs = rePkgs.cap(2).split(',');
113 }
114 else {
115 return;
116 }
117
118 for(int i = 0 ; i < pkgs.count() && i < args.count() ; i++) {
119 const QString packageName = pkgs.at(i);
120 if(packageName.isEmpty()) {
121 continue;
122 }
123 pkg.name = packageName;
124 pkg.arguments = args.at(i);
125 packages.append(pkg);
126 }
127
128 }
129
extract(const QString & key,Command & cmd)130 void SymbolView::extract(const QString& key, Command &cmd)
131 {
132 if (key.isEmpty()) {
133 return;
134 }
135 QStringList contents = key.split('%');
136 QString packages;
137
138 cmd.referenceCount = contents.at(0).toInt();
139 cmd.latexCommand = contents.at(1);
140 cmd.unicodeCommand = contents.at(2);
141
142 extractPackageString(contents.at(3), cmd.unicodePackages);
143 extractPackageString(contents.at(4), cmd.packages);
144 cmd.comment = contents.at(5);
145 cmd.path = contents.at(6);
146 }
147
initPage(int page)148 void SymbolView::initPage(int page)
149 {
150 switch(page) {
151 case MFUS:
152 fillWidget(MFUS_PREFIX);
153 break;
154
155 case Relation:
156 fillWidget("relation");
157 break;
158
159 case Operator:
160 fillWidget("operators");
161 break;
162
163 case Arrow:
164 fillWidget("arrows");
165 break;
166
167 case MiscMath:
168 fillWidget("misc-math");
169 break;
170
171 case MiscText:
172 fillWidget("misc-text");
173 break;
174
175 case Delimiters:
176 fillWidget("delimiters");
177 break;
178
179 case Greek:
180 fillWidget("greek");
181 break;
182
183 case Special:
184 fillWidget("special");
185 break;
186
187 case Cyrillic:
188 fillWidget("cyrillic");
189 break;
190
191 case User:
192 fillWidget("user");
193 break;
194
195 default:
196 qWarning() << "wrong argument in initPage()";
197 break;
198 }
199 }
200
getToolTip(const QString & key)201 QString SymbolView::getToolTip(const QString &key)
202 {
203 Command cmd;
204 extract(key, cmd);
205
206 QString label = "<p style='white-space:pre'>";
207 label += "<b>" + i18n("Command: %1", cmd.latexCommand.toHtmlEscaped()) + "</b>";
208 if(!cmd.unicodeCommand.isEmpty()) {
209 label += i18n("<br/>Unicode: %1", cmd.unicodeCommand.toHtmlEscaped());
210 }
211
212 if(cmd.packages.count() > 0) {
213 QString packageString;
214
215 if(cmd.packages.count() == 1) {
216 Package pkg = cmd.packages.at(0);
217 if(!pkg.arguments.isEmpty()) {
218 packageString += '[' + pkg.arguments + ']' + pkg.name;
219 }
220 else {
221 packageString += pkg.name;
222 }
223 }
224 else {
225 packageString = "<ul>";
226 for (int i = 0; i < cmd.packages.count() ; ++i) {
227 Package pkg = cmd.packages.at(i);
228 if(!pkg.arguments.isEmpty()) {
229 packageString += "<li>[" + pkg.arguments + ']' + pkg.name + "</li>";
230 }
231 else {
232 packageString += "<li>" + pkg.name + "</li>";
233 }
234 }
235 packageString += "</ul>";
236 }
237 label += "<br/>" + i18np("Required Package: %2", "Required Packages: %2", cmd.packages.count(), packageString);
238 }
239
240 if(!cmd.comment.isEmpty()) {
241 label += "<br/><i>" + i18n("Comment: %1", cmd.comment.toHtmlEscaped()) + "</i>";
242 }
243
244 label += "</p>";
245 return label;
246 }
247
mousePressEvent(QMouseEvent * event)248 void SymbolView::mousePressEvent(QMouseEvent *event)
249 {
250 Command cmd;
251 QString code_symbol;
252 QList<Package> packages;
253 QListWidgetItem *item = Q_NULLPTR;
254 bool math = false, bracket = false;
255
256 if(event->button() == Qt::LeftButton && (item = itemAt(event->pos()))) {
257 bracket = event->modifiers() & Qt::ControlModifier;
258 math = event->modifiers() & Qt::ShiftModifier;
259
260 extract(item->data(Qt::UserRole).toString(), cmd);
261 if(KileConfig::symbolViewUTF8()) {
262 code_symbol = cmd.unicodeCommand;
263 if(code_symbol.isEmpty()) {
264 code_symbol = cmd.latexCommand;
265 }
266 packages = cmd.unicodePackages;
267 }
268 else {
269 code_symbol = cmd.latexCommand;
270 packages = cmd.packages;
271 }
272
273 if(math != bracket) {
274 if(math) {
275 code_symbol = '$' + code_symbol + '$';
276 }
277 else if(bracket) {
278 code_symbol = '{' + code_symbol + '}';
279 }
280 }
281 emit(insertText(code_symbol, packages));
282 emit(addToList(item));
283 m_ki->focusEditor();
284 }
285
286 KILE_DEBUG_MAIN << "math is " << math << ", bracket is " << bracket << " and item->data(Qt::UserRole).toString() is " << (item ? item->data(Qt::UserRole).toString() : "");
287 }
288
convertLatin1StringtoUTF8(const QString & string)289 QString convertLatin1StringtoUTF8(const QString &string)
290 {
291 if(string.isEmpty()) {
292 return QString();
293 }
294
295 QVector<uint> stringAsIntVector;
296 QStringList stringList = string.split(',', QString::SkipEmptyParts);
297
298 QStringList::const_iterator it;
299 QString str;
300 bool ok;
301 int stringAsInt;
302 for(it = stringList.constBegin(); it != stringList.constEnd(); it++) {
303 str = *it;
304 str.remove("U+");
305 stringAsInt = str.toInt(&ok);
306 if(!ok) {
307 return QString();
308 }
309 stringAsIntVector.append(stringAsInt);
310 }
311
312 return QString::fromUcs4(stringAsIntVector.data(),stringAsIntVector.count());
313 }
314
fillWidget(const QString & prefix)315 void SymbolView::fillWidget(const QString& prefix)
316 {
317 KILE_DEBUG_MAIN << "===SymbolView::fillWidget(const QString& " << prefix << " )===";
318 QImage image;
319 QListWidgetItem* item;
320 QStringList refCnts, paths, unicodeValues;
321 QString key;
322
323 // find paths
324 if (prefix == MFUS_PREFIX) { // case: most frequently used symbols
325 KConfigGroup config = KSharedConfig::openConfig()->group(MFUS_GROUP);
326 QString configPaths = config.readEntry("paths");
327 QString configrefCnts = config.readEntry("counts");
328 paths = configPaths.split(',', QString::SkipEmptyParts);
329 refCnts = configrefCnts.split(',', QString::SkipEmptyParts);
330 KILE_DEBUG_MAIN << "Read " << paths.count() << " paths and " << refCnts.count() << " refCnts";
331 if(paths.count() != refCnts.count()) {
332 KILE_DEBUG_MAIN << "error in saved LRU list";
333 paths.clear();
334 refCnts.clear();
335 }
336 }
337 else { // case: any other group of math symbols
338 const QStringList dirs = KileUtilities::locateAll(QStandardPaths::AppDataLocation,
339 QLatin1String("mathsymbols/") + prefix,
340 QStandardPaths::LocateDirectory);
341 for(const QString &dir : dirs) {
342 const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.png"));
343 for(const QString &file : fileNames) {
344 const QString path = dir + '/' + file;
345 if (!paths.contains(path)) {
346 paths.append(path);
347 }
348 }
349 }
350 paths.sort();
351 for (int i = 0; i < paths.count(); i++) {
352 refCnts.append("1");
353 }
354 }
355
356 // render symbols
357 for (int i = 0; i < paths.count(); i++) {
358 if (image.load(paths[i])) {
359 item = new QListWidgetItem(this);
360
361 key = refCnts[i] + '%' + image.text("Command");
362 key += '%' + convertLatin1StringtoUTF8(image.text("CommandUnicode"));
363 key += '%' + image.text("UnicodePackages");
364 key += '%' + image.text("Packages");
365 key += '%' + convertLatin1StringtoUTF8(image.text("Comment"));
366 key += '%' + paths[i];
367
368 item->setData(Qt::UserRole, key);
369 item->setToolTip(getToolTip(key));
370
371 if (prefix != QLatin1String("user")) {
372 if (image.format() != QImage::Format_ARGB32_Premultiplied && image.format() != QImage::Format_ARGB32) {
373 image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
374 }
375
376 QPainter p;
377 p.begin(&image);
378 p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
379 p.fillRect(image.rect(), m_brush.brush(QPalette::Active));
380 p.end();
381 }
382 item->setIcon(QPixmap::fromImage(image));
383 }
384 else {
385 KILE_DEBUG_MAIN << "Loading file " << paths[i] << " failed";
386 }
387 }
388 }
389
writeConfig()390 void SymbolView::writeConfig()
391 {
392 QListWidgetItem *item;
393 QStringList paths;
394 QList<int> refCnts;
395 Command cmd;
396
397 KConfigGroup grp = KSharedConfig::openConfig()->group(MFUS_GROUP);
398
399 if (KileConfig::clearMFUS()) {
400 grp.deleteEntry("paths");
401 grp.deleteEntry("counts");
402 }
403 else {
404 for(int i = 0; i < count(); ++i) {
405 item = this->item(i);
406 extract(item->data(Qt::UserRole).toString(),cmd);
407 refCnts.append(cmd.referenceCount);
408 paths.append(cmd.path);
409 KILE_DEBUG_MAIN << "path=" << paths.last() << ", count is " << refCnts.last();
410 }
411 grp.writeEntry("paths", paths);
412 grp.writeEntry("counts", refCnts);
413 }
414 }
415
slotAddToList(const QListWidgetItem * item)416 void SymbolView::slotAddToList(const QListWidgetItem *item)
417 {
418 if(!item || item->icon().isNull()) {
419 return;
420 }
421
422 QListWidgetItem *tmpItem = Q_NULLPTR;
423 bool found = false;
424 const QRegExp reCnt("^\\d+");
425
426 KILE_DEBUG_MAIN << "===void SymbolView::slotAddToList(const QIconViewItem *" << item << " )===";
427
428 for(int i = 0; i < count(); ++i) {
429 tmpItem = this->item(i);
430 if (item->data(Qt::UserRole).toString().section('%', 1) == tmpItem->data(Qt::UserRole).toString().section('%', 1)) {
431 found = true;
432 break;
433 }
434 }
435
436 if(!found
437 && static_cast<unsigned int>(this->count() + 1) > KileConfig::numSymbolsMFUS()) { // we check before adding the symbol
438 int refCnt, minRefCnt = 10000;
439 QListWidgetItem *unpopularItem = Q_NULLPTR;
440
441 KILE_DEBUG_MAIN << "Removing most unpopular item";
442
443 for (int i = 0; i < count(); ++i) {
444 tmpItem = this->item(i);
445 extract(tmpItem->data(Qt::UserRole).toString(), refCnt);
446
447 if (refCnt < minRefCnt) {
448 refCnt = minRefCnt;
449 unpopularItem = tmpItem;
450 }
451 }
452 KILE_DEBUG_MAIN << " minRefCnt is " << minRefCnt;
453 delete unpopularItem;
454 }
455
456 if(found) {
457 KILE_DEBUG_MAIN << "item is already in the iconview";
458
459 int refCnt;
460 extract(tmpItem->data(Qt::UserRole).toString(), refCnt);
461
462 QString key = tmpItem->data(Qt::UserRole).toString();
463 key.replace(reCnt, QString::number(refCnt + 1));
464 tmpItem->setData(Qt::UserRole, key);
465 tmpItem->setToolTip(getToolTip(key));
466 }
467 else {
468 tmpItem = new QListWidgetItem(this);
469 tmpItem->setIcon(item->icon());
470 QString key = item->data(Qt::UserRole).toString();
471 tmpItem->setData(Qt::UserRole, key);
472 tmpItem->setToolTip(getToolTip(key));
473 }
474 }
475
476 }
477
478