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