File indexing completed on 2024-04-28 16:26:35

0001 /****************************************************************************************
0002     begin                : Fri Aug 1 2003
0003     copyright            : (C) 2003 by Jeroen Wijnhout (Jeroen.Wijnhout@kdemail.net)
0004                                2006 - 2009 by Thomas Braun
0005                                2012 - 2022 by Michel Ludwig (michel.ludwig@kdemail.net)
0006  ****************************************************************************************/
0007 
0008 /***************************************************************************
0009  *                                                                         *
0010  *   This program is free software; you can redistribute it and/or modify  *
0011  *   it under the terms of the GNU General Public License as published by  *
0012  *   the Free Software Foundation; either version 2 of the License, or     *
0013  *   (at your option) any later version.                                   *
0014  *                                                                         *
0015  ***************************************************************************/
0016 
0017 /*
0018 dani 2005-11-22
0019   - add some new symbols
0020   - rearranged source
0021 
0022 tbraun 2006-07-01
0023    - added tooltips which show the keys, copied from kfileiconview
0024    - reorganized the hole thing, more flexible png loading, removing the old big code_array, more groups
0025 
0026 tbraun 2007-06-04
0027     - Send a warning in the logwidget if needed packages are not included for the command
0028 tbraun 2007-06-13
0029     - Added Most frequently used symbolview, including remembering icons upon restart, removing of least popular item and configurable max item count
0030 */
0031 
0032 
0033 #include "symbolview.h"
0034 
0035 #include <QApplication>
0036 #include <QMouseEvent>
0037 #include <QPixmap>
0038 #include <QPainter>
0039 #include <QRegExp>
0040 #include <QStringList>
0041 #include <QTextDocument>
0042 
0043 #include <KColorScheme>
0044 #include <KConfig>
0045 #include <KLocalizedString>
0046 
0047 #include "kileconfig.h"
0048 #include "kiledebug.h"
0049 #include "kileinfo.h"
0050 #include "../symbolviewclasses.h"
0051 #include "utilities.h"
0052 
0053 #define MFUS_GROUP "MostFrequentlyUsedSymbols"
0054 #define MFUS_PREFIX "MFUS"
0055 
0056 
0057 namespace KileWidget {
0058 
0059 SymbolView::SymbolView(KileInfo *kileInfo, QWidget *parent, int type, const char *name)
0060     : QListWidget(parent), m_ki(kileInfo)
0061 {
0062     setObjectName(name);
0063     setViewMode(IconMode);
0064     setGridSize(QSize(36, 36));
0065     setSpacing(5);
0066     setWordWrap(false);
0067     setResizeMode(Adjust);
0068     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0069     setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0070     setMovement(Static);
0071     setSortingEnabled(false);
0072     setFlow(LeftToRight);
0073     setDragDropMode(NoDragDrop);
0074     m_brush = KStatefulBrush(KColorScheme::View, KColorScheme::NormalText);
0075     initPage(type);
0076 }
0077 
0078 SymbolView::~SymbolView()
0079 {
0080 }
0081 
0082 /* key format
0083 from old symbols with package info
0084 1%\textonequarter%%%{textcomp}%%/home/kdedev/.kde4/share/apps/kile/mathsymbols/misc-text/img072misc-text.png
0085 from old symbols without package info
0086 1%\oldstylenums{9}%%%%%/home/kdedev/.kde4/share/apps/kile/mathsymbols/misc-text/img070misc-text.png
0087 new symbol
0088 1%\neq%≠%[utf8x,,]{inputenc,ucs,}%[fleqn,]{amsmath,}%This command gives nice weather!%/home/kdedev/.kde4/share/apps/kile/mathsymbols/user/img002math.png
0089 */
0090 
0091 void SymbolView::extract(const QString& key, int& refCnt)
0092 {
0093     if (!key.isEmpty()) {
0094         refCnt = key.section('%', 0, 0).toInt();
0095     }
0096 }
0097 
0098 void SymbolView::extractPackageString(const QString&string, QList<Package> &packages)
0099 {
0100     QRegExp rePkgs("(?:\\[(.*)\\])?\\{(.*)\\}");
0101     QStringList args,pkgs;
0102     Package pkg;
0103 
0104     if(string.isEmpty()) {
0105         return;
0106     }
0107 
0108     packages.clear();
0109 
0110     if(rePkgs.exactMatch(string)) {
0111         args = rePkgs.cap(1).split(',');
0112         pkgs = rePkgs.cap(2).split(',');
0113     }
0114     else {
0115         return;
0116     }
0117 
0118     for(int i = 0 ; i < pkgs.count() && i < args.count() ; i++) {
0119         const QString packageName = pkgs.at(i);
0120         if(packageName.isEmpty()) {
0121             continue;
0122         }
0123         pkg.name = packageName;
0124         pkg.arguments = args.at(i);
0125         packages.append(pkg);
0126     }
0127 
0128 }
0129 
0130 void SymbolView::extract(const QString& key, Command &cmd)
0131 {
0132     if (key.isEmpty()) {
0133         return;
0134     }
0135     QStringList contents = key.split('%');
0136 
0137     cmd.referenceCount = contents.at(0).toInt();
0138     cmd.latexCommand = contents.at(1);
0139     cmd.unicodeCommand = contents.at(2);
0140 
0141     extractPackageString(contents.at(3), cmd.unicodePackages);
0142     extractPackageString(contents.at(4), cmd.packages);
0143     cmd.comment = contents.at(5);
0144     cmd.path = contents.at(6);
0145 }
0146 
0147 void SymbolView::initPage(int page)
0148 {
0149     switch(page) {
0150     case MFUS:
0151         fillWidget(MFUS_PREFIX);
0152         break;
0153 
0154     case Relation:
0155         fillWidget("relation");
0156         break;
0157 
0158     case Operator:
0159         fillWidget("operators");
0160         break;
0161 
0162     case Arrow:
0163         fillWidget("arrows");
0164         break;
0165 
0166     case MiscMath:
0167         fillWidget("misc-math");
0168         break;
0169 
0170     case MiscText:
0171         fillWidget("misc-text");
0172         break;
0173 
0174     case Delimiters:
0175         fillWidget("delimiters");
0176         break;
0177 
0178     case Greek:
0179         fillWidget("greek");
0180         break;
0181 
0182     case Special:
0183         fillWidget("special");
0184         break;
0185 
0186     case Cyrillic:
0187         fillWidget("cyrillic");
0188         break;
0189 
0190     case User:
0191         fillWidget("user");
0192         break;
0193 
0194     default:
0195         qWarning() << "wrong argument in initPage()";
0196         break;
0197     }
0198 }
0199 
0200 QString SymbolView::getToolTip(const QString &key)
0201 {
0202     Command cmd;
0203     extract(key, cmd);
0204 
0205     QString label = "<p style='white-space:pre'>";
0206     label += "<b>" + i18n("Command: %1", cmd.latexCommand.toHtmlEscaped()) + "</b>";
0207     if(!cmd.unicodeCommand.isEmpty()) {
0208         label += i18n("<br/>Unicode: %1", cmd.unicodeCommand.toHtmlEscaped());
0209     }
0210 
0211     if(cmd.packages.count() > 0) {
0212         QString packageString;
0213 
0214         if(cmd.packages.count() == 1) {
0215             Package pkg = cmd.packages.at(0);
0216             if(!pkg.arguments.isEmpty()) {
0217                 packageString += '[' + pkg.arguments + ']' + pkg.name;
0218             }
0219             else {
0220                 packageString += pkg.name;
0221             }
0222         }
0223         else {
0224             packageString = "<ul>";
0225             for (int i = 0; i < cmd.packages.count() ; ++i) {
0226                 Package pkg = cmd.packages.at(i);
0227                 if(!pkg.arguments.isEmpty()) {
0228                     packageString += "<li>[" + pkg.arguments + ']' + pkg.name + "</li>";
0229                 }
0230                 else {
0231                     packageString += "<li>" + pkg.name + "</li>";
0232                 }
0233             }
0234             packageString += "</ul>";
0235         }
0236         label += "<br/>" + i18np("Required Package: %2", "Required Packages: %2", cmd.packages.count(), packageString);
0237     }
0238 
0239     if(!cmd.comment.isEmpty()) {
0240         label += "<br/><i>" + i18n("Comment: %1", cmd.comment.toHtmlEscaped())  + "</i>";
0241     }
0242 
0243     label += "</p>";
0244     return label;
0245 }
0246 
0247 void SymbolView::mousePressEvent(QMouseEvent *event)
0248 {
0249     Command cmd;
0250     QListWidgetItem *item = Q_NULLPTR;
0251     bool math = false, bracket = false;
0252 
0253     if(event->button() == Qt::LeftButton && (item = itemAt(event->pos()))) {
0254         bracket = event->modifiers() & Qt::ControlModifier;
0255         math = event->modifiers() & Qt::ShiftModifier;
0256 
0257         QString code_symbol;
0258         QList<Package> packages;
0259         extract(item->data(Qt::UserRole).toString(), cmd);
0260         if(KileConfig::symbolViewUTF8()) {
0261             code_symbol = cmd.unicodeCommand;
0262             if(code_symbol.isEmpty()) {
0263                 code_symbol = cmd.latexCommand;
0264             }
0265             packages = cmd.unicodePackages;
0266         }
0267         else {
0268             code_symbol = cmd.latexCommand;
0269             packages = cmd.packages;
0270         }
0271 
0272         if(math != bracket) {
0273             if(math) {
0274                 code_symbol = '$' + code_symbol + '$';
0275             }
0276             else if(bracket) {
0277                 code_symbol = '{' + code_symbol + '}';
0278             }
0279         }
0280         emit(insertText(code_symbol, packages));
0281         emit(addToList(item));
0282         m_ki->focusEditor();
0283     }
0284 
0285     KILE_DEBUG_MAIN << "math is " << math << ", bracket is " << bracket << " and item->data(Qt::UserRole).toString() is " << (item ? item->data(Qt::UserRole).toString() : "");
0286 }
0287 
0288 QString convertLatin1StringtoUTF8(const QString &string)
0289 {
0290     if(string.isEmpty()) {
0291         return QString();
0292     }
0293 
0294     QVector<uint> stringAsIntVector;
0295     const QStringList stringList = string.split(',', Qt::SkipEmptyParts);
0296 
0297     for (QString str : stringList) {
0298         str.remove("U+");
0299         bool ok;
0300         int stringAsInt = str.toInt(&ok);
0301         if(!ok) {
0302             return QString();
0303         }
0304         stringAsIntVector.append(stringAsInt);
0305     }
0306 
0307     return QString::fromUcs4(stringAsIntVector.data(),stringAsIntVector.count());
0308 }
0309 
0310 void SymbolView::fillWidget(const QString& prefix)
0311 {
0312     KILE_DEBUG_MAIN << "===SymbolView::fillWidget(const QString& " << prefix <<  " )===";
0313     QImage image;
0314     QListWidgetItem* item;
0315     QStringList refCnts, paths;
0316     QString key;
0317 
0318     // find paths
0319     if (prefix == MFUS_PREFIX) { // case: most frequently used symbols
0320         KConfigGroup config = KSharedConfig::openConfig()->group(MFUS_GROUP);
0321         QString configPaths = config.readEntry("paths");
0322         QString configrefCnts = config.readEntry("counts");
0323         paths = configPaths.split(',', Qt::SkipEmptyParts);
0324         refCnts = configrefCnts.split(',', Qt::SkipEmptyParts);
0325         KILE_DEBUG_MAIN << "Read " << paths.count() << " paths and " << refCnts.count() << " refCnts";
0326         if(paths.count() != refCnts.count()) {
0327             KILE_DEBUG_MAIN << "error in saved LRU list";
0328             paths.clear();
0329             refCnts.clear();
0330         }
0331     }
0332     else { // case: any other group of math symbols
0333         const QStringList dirs = KileUtilities::locateAll(QStandardPaths::AppDataLocation,
0334                                                           QLatin1String("mathsymbols/") + prefix,
0335                                                           QStandardPaths::LocateDirectory);
0336         for(const QString &dir : dirs) {
0337             const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.png"));
0338             for(const QString &file : fileNames) {
0339                 const QString path = dir + '/' + file;
0340                 if (!paths.contains(path)) {
0341                     paths.append(path);
0342                 }
0343             }
0344         }
0345         paths.sort();
0346         for (int i = 0; i < paths.count(); i++) {
0347             refCnts.append("1");
0348         }
0349     }
0350 
0351     // render symbols
0352     for (int i = 0; i < paths.count(); i++) {
0353         if (image.load(paths[i])) {
0354             item = new QListWidgetItem(this);
0355 
0356             key = refCnts[i] + '%' + image.text("Command");
0357             key += '%' + convertLatin1StringtoUTF8(image.text("CommandUnicode"));
0358             key += '%' + image.text("UnicodePackages");
0359             key += '%' + image.text("Packages");
0360             key += '%' + convertLatin1StringtoUTF8(image.text("Comment"));
0361             key += '%' + paths[i];
0362 
0363             item->setData(Qt::UserRole, key);
0364             item->setToolTip(getToolTip(key));
0365 
0366             if (prefix != QLatin1String("user")) {
0367                 if (image.format() != QImage::Format_ARGB32_Premultiplied && image.format() != QImage::Format_ARGB32) {
0368                     image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
0369                 }
0370 
0371                 QPainter p;
0372                 p.begin(&image);
0373                 p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
0374                 p.fillRect(image.rect(), m_brush.brush(QPalette::Active));
0375                 p.end();
0376             }
0377             item->setIcon(QPixmap::fromImage(image));
0378         }
0379         else {
0380             KILE_DEBUG_MAIN << "Loading file " << paths[i] << " failed";
0381         }
0382     }
0383 }
0384 
0385 void SymbolView::writeConfig()
0386 {
0387     Command cmd;
0388 
0389     KConfigGroup grp = KSharedConfig::openConfig()->group(MFUS_GROUP);
0390 
0391     if (KileConfig::clearMFUS()) {
0392         grp.deleteEntry("paths");
0393         grp.deleteEntry("counts");
0394     }
0395     else {
0396         QStringList paths;
0397         QList<int> refCnts;
0398         for(int i = 0; i < count(); ++i) {
0399             QListWidgetItem *item = this->item(i);
0400             extract(item->data(Qt::UserRole).toString(),cmd);
0401             refCnts.append(cmd.referenceCount);
0402             paths.append(cmd.path);
0403             KILE_DEBUG_MAIN << "path=" << paths.last() << ", count is " << refCnts.last();
0404         }
0405         grp.writeEntry("paths", paths);
0406         grp.writeEntry("counts", refCnts);
0407     }
0408 }
0409 
0410 void SymbolView::slotAddToList(const QListWidgetItem *item)
0411 {
0412     if(!item || item->icon().isNull()) {
0413         return;
0414     }
0415 
0416     QListWidgetItem *tmpItem = Q_NULLPTR;
0417     bool found = false;
0418     const QRegExp reCnt("^\\d+");
0419 
0420     KILE_DEBUG_MAIN << "===void SymbolView::slotAddToList(const QIconViewItem *" << item << " )===";
0421 
0422     for(int i = 0; i < count(); ++i) {
0423         tmpItem = this->item(i);
0424         if (item->data(Qt::UserRole).toString().section('%', 1) == tmpItem->data(Qt::UserRole).toString().section('%', 1)) {
0425             found = true;
0426             break;
0427         }
0428     }
0429 
0430     if(!found
0431             && static_cast<unsigned int>(this->count() + 1) > KileConfig::numSymbolsMFUS()) {   // we check before adding the symbol
0432         int refCnt, minRefCnt = 10000;
0433         QListWidgetItem *unpopularItem = Q_NULLPTR;
0434 
0435         KILE_DEBUG_MAIN << "Removing most unpopular item";
0436 
0437         for (int i = 0; i < count(); ++i) {
0438             tmpItem = this->item(i);
0439             extract(tmpItem->data(Qt::UserRole).toString(), refCnt);
0440 
0441             if (refCnt < minRefCnt) {
0442                 refCnt = minRefCnt;
0443                 unpopularItem = tmpItem;
0444             }
0445         }
0446         KILE_DEBUG_MAIN << " minRefCnt is " << minRefCnt;
0447         delete unpopularItem;
0448     }
0449 
0450     if(found) {
0451         KILE_DEBUG_MAIN << "item is already in the iconview";
0452 
0453         int refCnt;
0454         extract(tmpItem->data(Qt::UserRole).toString(), refCnt);
0455 
0456         QString key = tmpItem->data(Qt::UserRole).toString();
0457         key.replace(reCnt, QString::number(refCnt + 1));
0458         tmpItem->setData(Qt::UserRole, key);
0459         tmpItem->setToolTip(getToolTip(key));
0460     }
0461     else {
0462         tmpItem = new QListWidgetItem(this);
0463         tmpItem->setIcon(item->icon());
0464         QString key = item->data(Qt::UserRole).toString();
0465         tmpItem->setData(Qt::UserRole, key);
0466         tmpItem->setToolTip(getToolTip(key));
0467     }
0468 }
0469 
0470 }
0471