File indexing completed on 2024-04-14 15:51:07

0001 /**
0002  * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût <slaout@linux62.org>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "tools.h"
0008 #include "tag.h"
0009 
0010 #include <QDebug>
0011 #include <QGuiApplication>
0012 #include <QtCore/QDir>
0013 #include <QtCore/QFileInfo>
0014 #include <QtCore/QList>
0015 #include <QtCore/QMimeData>
0016 #include <QtCore/QObject>
0017 #include <QtCore/QRegExp>
0018 #include <QtCore/QRegularExpression>
0019 #include <QtCore/QString>
0020 #include <QtCore/QTime>
0021 #include <QtGui/QFont>
0022 #include <QtGui/QFontInfo>
0023 #include <QtGui/QImage>
0024 #include <QtGui/QPixmap>
0025 
0026 #include <QTextBlock>
0027 #include <QtGui/QTextDocument>
0028 
0029 #include <KIO/CopyJob> //For KIO::trash
0030 #include <KLocalizedString>
0031 
0032 #include "config.h"
0033 #include "debugwindow.h"
0034 
0035 // cross reference
0036 #include "bnpview.h"
0037 #include "global.h"
0038 #include "htmlexporter.h"
0039 #include "linklabel.h"
0040 
0041 #include <langinfo.h>
0042 
0043 QVector<QTime> StopWatch::starts;
0044 QVector<double> StopWatch::totals;
0045 QVector<uint> StopWatch::counts;
0046 
0047 void StopWatch::start(int id)
0048 {
0049     if (id >= starts.size()) {
0050         totals.resize(id + 1);
0051         counts.resize(id + 1);
0052         for (int i = starts.size(); i <= id; i++) {
0053             totals[i] = 0;
0054             counts[i] = 0;
0055         }
0056         starts.resize(id + 1);
0057     }
0058     starts[id] = QTime::currentTime();
0059 }
0060 
0061 void StopWatch::check(int id)
0062 {
0063     if (id >= starts.size())
0064         return;
0065     double time = starts[id].msecsTo(QTime::currentTime()) / 1000.0;
0066     totals[id] += time;
0067     counts[id]++;
0068     qDebug() << Q_FUNC_INFO << "Timer_" << id << ": " << time << " s    [" << counts[id] << " times, total: " << totals[id] << " s, average: " << totals[id] / counts[id] << " s]" << Qt::endl;
0069 }
0070 
0071 QString Tools::textToHTML(const QString &text)
0072 {
0073     if (text.isEmpty())
0074         return "<p></p>";
0075     if (/*text.isEmpty() ||*/ text == " " || text == "&nbsp;")
0076         return "<p>&nbsp;</p>";
0077 
0078     // convertFromPlainText() replace "\n\n" by "</p>\n<p>": we don't want that
0079     QString htmlString = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
0080     return htmlString.replace("</p>\n", "<br>\n<br>\n").replace("\n<p>", "\n"); // Don't replace first and last tags
0081 }
0082 
0083 QString Tools::textToHTMLWithoutP(const QString &text)
0084 {
0085     // textToHTML(text) return "<p>HTMLizedText</p>". We remove the strating "<p>" and ending </p>"
0086     QString HTMLizedText = textToHTML(text);
0087     return HTMLizedText.mid(3, HTMLizedText.length() - 3 - 4);
0088 }
0089 
0090 QString Tools::htmlToParagraph(const QString &html)
0091 {
0092     QString result = html;
0093 
0094     // Remove the <html> start tag, all the <head> and the <body> start
0095     QRegularExpression patternBodyTag("<body.*?>");
0096     QRegularExpressionMatch bodyTag = patternBodyTag.match(result);
0097 
0098     if (bodyTag.hasMatch())
0099     {
0100         result = result.mid(bodyTag.capturedEnd(0));
0101     }
0102 
0103     // Remove the ending "</p>\n</body></html>", each tag can be separated by space characters (%s)
0104     // "</p>" can be omitted (eg. if the HTML doesn't contain paragraph but tables), as well as "</body>" (optional)
0105     int pos = result.indexOf(QRegExp("(?:(?:</p>[\\s\\n\\r\\t]*)*</body>[\\s\\n\\r\\t]*)*</html>", Qt::CaseInsensitive));
0106     if (pos != -1)
0107         result = result.left(pos);
0108 
0109     return result;
0110 }
0111 
0112 // The following is adapted from KStringHanlder::tagURLs
0113 // The adaptation lies in the change to urlEx
0114 // Thanks to Richard Heck
0115 QString Tools::detectURLs(const QString &text)
0116 {
0117     QRegExp urlEx("<!DOCTYPE[^\"]+\"([^\"]+)\"[^\"]+\"([^\"]+)/([^/]+)\\.dtd\">");
0118     QString richText(text);
0119     int urlPos = 0;
0120     int urlLen;
0121     if ((urlPos = urlEx.indexIn(richText, urlPos)) >= 0)
0122         urlPos += urlEx.matchedLength();
0123     else
0124         urlPos = 0;
0125     urlEx.setPattern("(www\\.(?!\\.)|(fish|(f|ht)tp(|s))://)[\\d\\w\\./,:_~\\?=&;#@\\-\\+\\%\\$]+[\\d\\w/]");
0126     while ((urlPos = urlEx.indexIn(richText, urlPos)) >= 0) {
0127         urlLen = urlEx.matchedLength();
0128 
0129         // if this match is already a link don't convert it.
0130         if (richText.mid(urlPos - 6, 6) == "href=\"") {
0131             urlPos += urlLen;
0132             continue;
0133         }
0134 
0135         QString href = richText.mid(urlPos, urlLen);
0136         // we handle basket links separately...
0137         if (href.contains("basket://")) {
0138             urlPos += urlLen;
0139             continue;
0140         }
0141         // Qt doesn't support (?<=pattern) so we do it here
0142         if ((urlPos > 0) && richText[urlPos - 1].isLetterOrNumber()) {
0143             urlPos++;
0144             continue;
0145         }
0146         // Don't use QString::arg since %01, %20, etc could be in the string
0147         QString anchor = "<a href=\"" + href + "\">" + href + "</a>";
0148         richText.replace(urlPos, urlLen, anchor);
0149         urlPos += anchor.length();
0150     }
0151     return richText;
0152 }
0153 
0154 QString Tools::detectCrossReferences(const QString &text, bool userLink, HTMLExporter *exporter)
0155 {
0156     QString richText(text);
0157 
0158     int urlPos = 0;
0159     int urlLen;
0160 
0161     QRegExp urlEx("\\[\\[(.+)\\]\\]");
0162     urlEx.setMinimal(true);
0163     while ((urlPos = urlEx.indexIn(richText, urlPos)) >= 0) {
0164         urlLen = urlEx.matchedLength();
0165         QString href = urlEx.cap(1);
0166 
0167         QStringList hrefParts = href.split('|');
0168         QString anchor;
0169 
0170         if (exporter) // if we're exporting this basket to html.
0171             anchor = crossReferenceForHtml(hrefParts, exporter);
0172         else if (userLink) // the link is manually created (ie [[/top level/sub]] )
0173             anchor = crossReferenceForConversion(hrefParts);
0174         else // otherwise it's a standard link (ie. [[basket://basket107]] )
0175             anchor = crossReferenceForBasket(hrefParts);
0176 
0177         if (!anchor.isEmpty()) {
0178             richText.replace(urlPos, urlLen, anchor);
0179             urlPos += anchor.length();
0180 
0181         } else {
0182             urlPos += urlLen;
0183         }
0184     }
0185     return richText;
0186 }
0187 
0188 QList<State*> Tools::detectTags(const QString& text, int& prefixLength)
0189 {
0190     QList<State*> tagsDetected;
0191     prefixLength = 0;
0192     QRegularExpressionMatchIterator matchIt = Tag::regexpDetectTagsInPlainText().globalMatch(text);
0193     while (matchIt.hasNext())
0194     {
0195         QRegularExpressionMatch match = matchIt.next();
0196         prefixLength = match.capturedEnd(0);
0197         const QString& stateRepr = match.captured(1);
0198         State* state = Tag::stateByTextEquiv(stateRepr);
0199         if (state) tagsDetected.append(state);
0200     }
0201 
0202     //if tags are found, eat possible trailing whitespaces as well
0203     if (prefixLength)
0204     {
0205         prefixLength = text.indexOf(QRegularExpression("[^\\s]"), prefixLength);
0206         if (prefixLength == -1)
0207            prefixLength = text.length();
0208     }
0209     return tagsDetected;
0210 }
0211 
0212 QString Tools::crossReferenceForBasket(const QStringList& linkParts)
0213 {
0214     QString basketLink = linkParts.first();
0215     if (!basketLink.startsWith(QLatin1String("basket://"))) return QString();
0216 
0217     QString url = basketLink.mid(9, basketLink.length() - 9);
0218     if (url.isEmpty()) return QString();
0219 
0220     QString title = linkParts.last().trimmed();
0221     QString css = LinkLook::crossReferenceLook->toCSS("cross_reference", QColor());
0222     QString classes = "cross_reference";
0223 
0224     QString anchor = QString("<style>%1</style><a href=\"%2\" class=\"%3\">%4</a>")
0225         .arg(css)
0226         .arg(basketLink)
0227         .arg(classes)
0228         .arg(QUrl::fromPercentEncoding(title.toUtf8()));
0229 
0230     return anchor;
0231 }
0232 
0233 QString Tools::crossReferenceForHtml(const QStringList& linkParts, HTMLExporter *exporter)
0234 {
0235     QString basketLink = linkParts.first();
0236     QString title = linkParts.last().trimmed();
0237     if (!basketLink.startsWith(QLatin1String("basket://"))) return QString();
0238 
0239     QString url = basketLink.mid(9, basketLink.length() - 9);
0240     if (url.isEmpty()) return QString();
0241 
0242     BasketScene *basket = Global::bnpView->basketForFolderName(url);
0243 
0244     // remove the trailing slash.
0245     url = url.left(url.length() - 1);
0246 
0247     // if the basket we're trying to link to is the basket that was exported then
0248     // we have to use a special way to refer to it for the links.
0249     if (basket == exporter->exportedBasket)
0250         url = "../../" + exporter->fileName;
0251     else {
0252         // if we're in the exported basket then the links have to include
0253         // the sub directories.
0254         if (exporter->currentBasket == exporter->exportedBasket)
0255             url.prepend(exporter->basketsFolderName);
0256         if (!url.isEmpty())
0257             url.append(".html");
0258     }
0259 
0260     QString classes = "cross_reference";
0261     QString anchor = "<a href=\"" + url + "\" class=\"" + classes + "\">" + QUrl::fromPercentEncoding(title.toUtf8()) + "</a>";
0262     return anchor;
0263 }
0264 
0265 QString Tools::crossReferenceForConversion(const QStringList& linkParts)
0266 {
0267     QString basketLink = linkParts.first();
0268     QString title;
0269 
0270     if (basketLink.startsWith(QLatin1String("basket://")))
0271         return QString("[[%1|%2]]").arg(basketLink, linkParts.last());
0272 
0273     if (basketLink.endsWith('/'))
0274         basketLink = basketLink.left(basketLink.length() - 1);
0275 
0276     QStringList pages = basketLink.split('/');
0277 
0278     if (linkParts.count() <= 1)
0279         title = pages.last();
0280     else
0281         title = linkParts.last().trimmed();
0282 
0283     QString url = Global::bnpView->folderFromBasketNameLink(pages);
0284     if (url.isEmpty()) return QString();
0285 
0286     return QString("[[basket://%1|%2]]").arg(url, title);
0287 }
0288 
0289 QString Tools::htmlToText(const QString &html)
0290 {
0291     QString text = htmlToParagraph(html);
0292     text.remove('\n');
0293     text.replace("</h1>", "\n");
0294     text.replace("</h2>", "\n");
0295     text.replace("</h3>", "\n");
0296     text.replace("</h4>", "\n");
0297     text.replace("</h5>", "\n");
0298     text.replace("</h6>", "\n");
0299     text.replace("</li>", "\n");
0300     text.replace("</dt>", "\n");
0301     text.replace("</dd>", "\n");
0302     text.replace("<dd>", "   ");
0303     text.replace("</div>", "\n");
0304     text.replace("</blockquote>", "\n");
0305     text.replace("</caption>", "\n");
0306     text.replace("</tr>", "\n");
0307     text.replace("</th>", "  ");
0308     text.replace("</td>", "  ");
0309     text.replace("<br>", "\n");
0310     text.replace("<br />", "\n");
0311     text.replace("</p>", "\n");
0312     // FIXME: Format <table> tags better, if possible
0313     // TODO: Replace &eacute; and co. by their equivalent!
0314 
0315     // To manage tags:
0316     int pos = 0;
0317     int pos2;
0318     QString tag, tag3;
0319     // To manage lists:
0320     int deep = 0;     // The deep of the current line in imbriqued lists
0321     QList<bool> ul;   // true if current list is a <ul> one, false if it's an <ol> one
0322     QList<int> lines; // The line number if it is an <ol> list
0323     // We're removing every other tags, or replace them in the case of li:
0324     while ((pos = text.indexOf("<"), pos) != -1) {
0325         // What is the current tag?
0326         tag = text.mid(pos + 1, 2);
0327         tag3 = text.mid(pos + 1, 3);
0328         // Lists work:
0329         if (tag == "ul") {
0330             deep++;
0331             ul.push_back(true);
0332             lines.push_back(-1);
0333         } else if (tag == "ol") {
0334             deep++;
0335             ul.push_back(false);
0336             lines.push_back(0);
0337         } else if (tag3 == "/ul" || tag3 == "/ol") {
0338             deep--;
0339             ul.pop_back();
0340             lines.pop_back();
0341         }
0342         // Where the tag closes?
0343         pos2 = text.indexOf(">");
0344         if (pos2 != -1) {
0345             // Remove the tag:
0346             text.remove(pos, pos2 - pos + 1);
0347             // And replace li with "* ", "x. "... without forbidding to indent that:
0348             if (tag == "li") {
0349                 // How many spaces before the line (indentation):
0350                 QString spaces;
0351                 for (int i = 1; i < deep; i++)
0352                     spaces += QStringLiteral("  ");
0353                 // The bullet or number of the line:
0354                 QString bullet = "* ";
0355                 if (ul.back() == false) {
0356                     lines.push_back(lines.back() + 1);
0357                     lines.pop_back();
0358                     bullet = QString::number(lines.back()) + ". ";
0359                 }
0360                 // Insertion:
0361                 text.insert(pos, spaces + bullet);
0362             }
0363             if ((tag3 == "/ul" || tag3 == "/ol") && deep == 0)
0364                 text.insert(pos, "\n"); // Empty line before and after a set of lists
0365         }
0366         ++pos;
0367     }
0368 
0369     text.replace("&gt;", ">");
0370     text.replace("&lt;", "<");
0371     text.replace("&quot;", "\"");
0372     text.replace("&nbsp;", " ");
0373     text.replace("&amp;", "&"); // CONVERT IN LAST!!
0374 
0375     // HtmlContent produces "\n" for empty note
0376     if (text == "\n")
0377         text = QString();
0378 
0379     return text;
0380 }
0381 
0382 QString Tools::textDocumentToMinimalHTML(QTextDocument *document)
0383 {
0384     QFont originalFont = document->defaultFont();
0385     document->setDefaultFont(QFont());
0386     QString docContent = document->toHtml("utf-8");
0387     document->setDefaultFont(originalFont);
0388 
0389     //Tag styles appear in html output as body styles. Remove them to preserve internal formatting.
0390     QRegularExpression patternBodyTag("<body.*?>");
0391     QRegularExpressionMatch bodyTag = patternBodyTag.match(docContent);
0392 
0393     if (!bodyTag.hasMatch())
0394         return docContent;
0395 
0396     return docContent.replace(bodyTag.capturedStart(0), bodyTag.capturedLength(0), "<body>");
0397 }
0398 
0399 QString Tools::cssFontDefinition(const QFont &font, bool onlyFontFamily)
0400 {
0401     // The font definition:
0402     QString definition = font.key() + (font.italic() ? QStringLiteral("italic ") : QString()) + (font.bold() ? QStringLiteral("bold ") : QString()) + QString::number(QFontInfo(font).pixelSize()) + QStringLiteral("px ");
0403 
0404     // Then, try to match the font name with a standard CSS font family:
0405     QString genericFont;
0406     if (definition.contains("serif", Qt::CaseInsensitive) || definition.contains("roman", Qt::CaseInsensitive)) {
0407         genericFont = QStringLiteral("serif");
0408     }
0409     // No "else if" because "sans serif" must be counted as "sans". So, the order between "serif" and "sans" is important
0410     if (definition.contains("sans", Qt::CaseInsensitive) || definition.contains("arial", Qt::CaseInsensitive) || definition.contains("helvetica", Qt::CaseInsensitive)) {
0411         genericFont = QStringLiteral("sans-serif");
0412     }
0413     if (definition.contains("mono", Qt::CaseInsensitive) || definition.contains("courier", Qt::CaseInsensitive) || definition.contains("typewriter", Qt::CaseInsensitive) || definition.contains("console", Qt::CaseInsensitive) ||
0414         definition.contains("terminal", Qt::CaseInsensitive) || definition.contains("news", Qt::CaseInsensitive)) {
0415         genericFont = QStringLiteral("monospace");
0416     }
0417 
0418     // Eventually add the generic font family to the definition:
0419     QString fontDefinition = QStringLiteral("\"%1\"").arg(font.family());
0420     if (!genericFont.isEmpty())
0421         fontDefinition += ", " + genericFont;
0422 
0423     if (onlyFontFamily)
0424         return fontDefinition;
0425 
0426     return definition + fontDefinition;
0427 }
0428 
0429 QString Tools::stripEndWhiteSpaces(const QString &string)
0430 {
0431     uint length = string.length();
0432     uint i;
0433     for (i = length; i > 0; --i)
0434         if (!string[i - 1].isSpace())
0435             break;
0436     if (i == 0)
0437         return QString();
0438     else
0439         return string.left(i);
0440 }
0441 
0442 QString Tools::cssColorName(const QString& colorHex)
0443 {
0444     static const QMap<QString, QString> cssColors = {
0445         {"#00ffff", "aqua"    },
0446         {"#000000", "black"   },
0447         {"#0000ff", "blue"    },
0448         {"#ff00ff", "fuchsia" },
0449         {"#808080", "gray"    },
0450         {"#008000", "green"   },
0451         {"#00ff00", "lime"    },
0452         {"#800000", "maroon"  },
0453         {"#000080", "navy"    },
0454         {"#808000", "olive"   },
0455         {"#800080", "purple"  },
0456         {"#ff0000", "red"     },
0457         {"#c0c0c0", "silver"  },
0458         {"#008080", "teal"    },
0459         {"#ffffff", "white"   },
0460         {"#ffff00", "yellow"  },
0461         // CSS extended colors
0462         {"#f0f8ff", "aliceblue"            },
0463         {"#faebd7", "antiquewhite"         },
0464         {"#7fffd4", "aquamarine"           },
0465         {"#f0ffff", "azure"                },
0466         {"#f5f5dc", "beige"                },
0467         {"#ffe4c4", "bisque"               },
0468         {"#ffebcd", "blanchedalmond"       },
0469         {"#8a2be2", "blueviolet"           },
0470         {"#a52a2a", "brown"                },
0471         {"#deb887", "burlywood"            },
0472         {"#5f9ea0", "cadetblue"            },
0473         {"#7fff00", "chartreuse"           },
0474         {"#d2691e", "chocolate"            },
0475         {"#ff7f50", "coral"                },
0476         {"#6495ed", "cornflowerblue"       },
0477         {"#fff8dc", "cornsilk"             },
0478         {"#dc1436", "crimson"              },
0479         {"#00ffff", "cyan"                 },
0480         {"#00008b", "darkblue"             },
0481         {"#008b8b", "darkcyan"             },
0482         {"#b8860b", "darkgoldenrod"        },
0483         {"#a9a9a9", "darkgray"             },
0484         {"#006400", "darkgreen"            },
0485         {"#bdb76b", "darkkhaki"            },
0486         {"#8b008b", "darkmagenta"          },
0487         {"#556b2f", "darkolivegreen"       },
0488         {"#ff8c00", "darkorange"           },
0489         {"#9932cc", "darkorchid"           },
0490         {"#8b0000", "darkred"              },
0491         {"#e9967a", "darksalmon"           },
0492         {"#8fbc8f", "darkseagreen"         },
0493         {"#483d8b", "darkslateblue"        },
0494         {"#2f4f4f", "darkslategray"        },
0495         {"#00ced1", "darkturquoise"        },
0496         {"#9400d3", "darkviolet"           },
0497         {"#ff1493", "deeppink"             },
0498         {"#00bfff", "deepskyblue"          },
0499         {"#696969", "dimgray"              },
0500         {"#1e90ff", "dodgerblue"           },
0501         {"#b22222", "firebrick"            },
0502         {"#fffaf0", "floralwhite"          },
0503         {"#228b22", "forestgreen"          },
0504         {"#dcdcdc", "gainsboro"            },
0505         {"#f8f8ff", "ghostwhite"           },
0506         {"#ffd700", "gold"                 },
0507         {"#daa520", "goldenrod"            },
0508         {"#adff2f", "greenyellow"          },
0509         {"#f0fff0", "honeydew"             },
0510         {"#ff69b4", "hotpink"              },
0511         {"#cd5c5c", "indianred"            },
0512         {"#4b0082", "indigo"               },
0513         {"#fffff0", "ivory"                },
0514         {"#f0e68c", "khaki"                },
0515         {"#e6e6fa", "lavender"             },
0516         {"#fff0f5", "lavenderblush"        },
0517         {"#7cfc00", "lawngreen"            },
0518         {"#fffacd", "lemonchiffon"         },
0519         {"#add8e6", "lightblue"            },
0520         {"#f08080", "lightcoral"           },
0521         {"#e0ffff", "lightcyan"            },
0522         {"#fafad2", "lightgoldenrodyellow" },
0523         {"#90ee90", "lightgreen"           },
0524         {"#d3d3d3", "lightgrey"            },
0525         {"#ffb6c1", "lightpink"            },
0526         {"#ffa07a", "lightsalmon"          },
0527         {"#20b2aa", "lightseagreen"        },
0528         {"#87cefa", "lightskyblue"         },
0529         {"#778899", "lightslategray"       },
0530         {"#b0c4de", "lightsteelblue"       },
0531         {"#ffffe0", "lightyellow"          },
0532         {"#32cd32", "limegreen"            },
0533         {"#faf0e6", "linen"                },
0534         {"#ff00ff", "magenta"              },
0535         {"#66cdaa", "mediumaquamarine"     },
0536         {"#0000cd", "mediumblue"           },
0537         {"#ba55d3", "mediumorchid"         },
0538         {"#9370db", "mediumpurple"         },
0539         {"#3cb371", "mediumseagreen"       },
0540         {"#7b68ee", "mediumslateblue"      },
0541         {"#00fa9a", "mediumspringgreen"    },
0542         {"#48d1cc", "mediumturquoise"      },
0543         {"#c71585", "mediumvioletred"      },
0544         {"#191970", "midnightblue"         },
0545         {"#f5fffa", "mintcream"            },
0546         {"#ffe4e1", "mistyrose"            },
0547         {"#ffe4b5", "moccasin"             },
0548         {"#ffdead", "navajowhite"          },
0549         {"#fdf5e6", "oldlace"              },
0550         {"#6b8e23", "olivedrab"            },
0551         {"#ffa500", "orange"               },
0552         {"#ff4500", "orangered"            },
0553         {"#da70d6", "orchid"               },
0554         {"#eee8aa", "palegoldenrod"        },
0555         {"#98fb98", "palegreen"            },
0556         {"#afeeee", "paleturquoise"        },
0557         {"#db7093", "palevioletred"        },
0558         {"#ffefd5", "papayawhip"           },
0559         {"#ffdab9", "peachpuff"            },
0560         {"#cd853f", "peru"                 },
0561         {"#ffc0cb", "pink"                 },
0562         {"#dda0dd", "plum"                 },
0563         {"#b0e0e6", "powderblue"           },
0564         {"#bc8f8f", "rosybrown"            },
0565         {"#4169e1", "royalblue"            },
0566         {"#8b4513", "saddlebrown"          },
0567         {"#fa8072", "salmon"               },
0568         {"#f4a460", "sandybrown"           },
0569         {"#2e8b57", "seagreen"             },
0570         {"#fff5ee", "seashell"             },
0571         {"#a0522d", "sienna"               },
0572         {"#87ceeb", "skyblue"              },
0573         {"#6a5acd", "slateblue"            },
0574         {"#708090", "slategray"            },
0575         {"#fffafa", "snow"                 },
0576         {"#00ff7f", "springgreen"          },
0577         {"#4682b4", "steelblue"            },
0578         {"#d2b48c", "tan"                  },
0579         {"#d8bfd8", "thistle"              },
0580         {"#ff6347", "tomato"               },
0581         {"#40e0d0", "turquoise"            },
0582         {"#ee82ee", "violet"               },
0583         {"#f5deb3", "wheat"                },
0584         {"#f5f5f5", "whitesmoke"           },
0585         {"#9acd32", "yellowgreen"          } };
0586 
0587     return cssColors.value(colorHex, QString());
0588 }
0589 
0590 
0591 bool Tools::isWebColor(const QColor &color)
0592 {
0593     int r = color.red();   // The 216 web colors are those colors whose RGB (Red, Green, Blue)
0594     int g = color.green(); //  values are all in the set (0, 51, 102, 153, 204, 255).
0595     int b = color.blue();
0596 
0597     return ((r == 0 || r == 51 || r == 102 || r == 153 || r == 204 || r == 255) &&
0598             (g == 0 || g == 51 || g == 102 || g == 153 || g == 204 || g == 255) &&
0599             (b == 0 || b == 51 || b == 102 || b == 153 || b == 204 || b == 255));
0600 }
0601 
0602 QColor Tools::mixColor(const QColor &color1, const QColor &color2, const float ratio)
0603 {
0604     QColor mixedColor;
0605     mixedColor.setRgb((color1.red() * ratio + color2.red()) / (1 + ratio),
0606                       (color1.green() * ratio + color2.green()) / (1 + ratio),
0607                       (color1.blue() * ratio + color2.blue()) / (1 + ratio));
0608     return mixedColor;
0609 }
0610 
0611 bool Tools::tooDark(const QColor &color)
0612 {
0613     return color.value() < 175;
0614 }
0615 
0616 // TODO: Use it for all indentPixmap()
0617 QPixmap Tools::normalizePixmap(const QPixmap &pixmap, int width, int height)
0618 {
0619     if (height <= 0)
0620         height = width;
0621 
0622     if (pixmap.isNull() || (pixmap.width() == width && pixmap.height() == height))
0623         return pixmap;
0624 
0625     return pixmap;
0626 }
0627 
0628 QPixmap Tools::indentPixmap(const QPixmap &source, int depth, int deltaX)
0629 {
0630     // Verify if it is possible:
0631     if (depth <= 0 || source.isNull())
0632         return source;
0633 
0634     // Compute the number of pixels to indent:
0635     if (deltaX <= 0)
0636         deltaX = 2 * source.width() / 3;
0637     int indent = depth * deltaX;
0638 
0639     // Create the images:
0640     QImage resultImage(indent + source.width(), source.height(), QImage::Format_ARGB32);
0641     resultImage.setColorCount(32);
0642 
0643     QImage sourceImage = source.toImage();
0644 
0645     // Clear the indent part (the left part) by making it fully transparent:
0646     uint *p;
0647     for (int row = 0; row < resultImage.height(); ++row) {
0648         for (int column = 0; column < resultImage.width(); ++column) {
0649             p = (uint *)resultImage.scanLine(row) + column;
0650             *p = 0; // qRgba(0, 0, 0, 0)
0651         }
0652     }
0653 
0654     // Copy the source image byte per byte to the right part:
0655     uint *q;
0656     for (int row = 0; row < sourceImage.height(); ++row) {
0657         for (int column = 0; column < sourceImage.width(); ++column) {
0658             p = (uint *)resultImage.scanLine(row) + indent + column;
0659             q = (uint *)sourceImage.scanLine(row) + column;
0660             *p = *q;
0661         }
0662     }
0663 
0664     // And return the result:
0665     QPixmap result = QPixmap::fromImage(resultImage);
0666     return result;
0667 }
0668 
0669 void Tools::deleteRecursively(const QString &folderOrFile)
0670 {
0671     if (folderOrFile.isEmpty())
0672         return;
0673 
0674     QFileInfo fileInfo(folderOrFile);
0675     if (fileInfo.isDir()) {
0676         // Delete the child files:
0677         QDir dir(folderOrFile, QString(), QDir::Name | QDir::IgnoreCase, QDir::TypeMask | QDir::Hidden);
0678         QStringList list = dir.entryList();
0679         for (QStringList::Iterator it = list.begin(); it != list.end(); ++it)
0680             if (*it != "." && *it != "..")
0681                 deleteRecursively(folderOrFile + '/' + *it);
0682         // And then delete the folder:
0683         dir.rmdir(folderOrFile);
0684     } else
0685         // Delete the file:
0686         QFile::remove(folderOrFile);
0687 }
0688 
0689 void Tools::deleteMetadataRecursively(const QString &folderOrFile)
0690 {
0691     QFileInfo fileInfo(folderOrFile);
0692     if (fileInfo.isDir()) {
0693         // Delete Metadata of the child files:
0694         QDir dir(folderOrFile, QString(), QDir::Name | QDir::IgnoreCase, QDir::TypeMask | QDir::Hidden);
0695         QStringList list = dir.entryList();
0696         for (QStringList::Iterator it = list.begin(); it != list.end(); ++it)
0697             if (*it != "." && *it != "..")
0698                 deleteMetadataRecursively(folderOrFile + '/' + *it);
0699     }
0700 }
0701 
0702 void Tools::trashRecursively(const QString &folderOrFile)
0703 {
0704     if (folderOrFile.isEmpty())
0705         return;
0706 
0707     KIO::trash(QUrl::fromLocalFile(folderOrFile), KIO::HideProgressInfo);
0708 }
0709 
0710 QString Tools::fileNameForNewFile(const QString &wantedName, const QString &destFolder)
0711 {
0712     QString fileName = wantedName;
0713     QString fullName = destFolder + fileName;
0714     QString extension = QString();
0715     int number = 2;
0716     QDir dir;
0717 
0718     // First check if the file do not exists yet (simpler and more often case)
0719     dir = QDir(fullName);
0720     if (!dir.exists(fullName))
0721         return fileName;
0722 
0723     // Find the file extension, if it exists : Split fileName in fileName and extension
0724     // Example : fileName == "note5-3.txt" => fileName = "note5-3" and extension = ".txt"
0725     int extIndex = fileName.lastIndexOf('.');
0726     if (extIndex != -1 && extIndex != int(fileName.length() - 1)) { // Extension found and fileName do not ends with '.' !
0727         extension = fileName.mid(extIndex);
0728         fileName.truncate(extIndex);
0729     } // else fileName = fileName and extension = QString()
0730 
0731     // Find the file number, if it exists : Split fileName in fileName and number
0732     // Example : fileName == "note5-3" => fileName = "note5" and number = 3
0733     int extNumber = fileName.lastIndexOf('-');
0734     if (extNumber != -1 && extNumber != int(fileName.length() - 1)) { // Number found and fileName do not ends with '-' !
0735         bool isANumber;
0736         int theNumber = fileName.mid(extNumber + 1).toInt(&isANumber);
0737         if (isANumber) {
0738             number = theNumber;
0739             fileName.truncate(extNumber);
0740         } // else :
0741     }     // else fileName = fileName and number = 2 (because if the file already exists, the generated name is at last the 2nd)
0742 
0743     QString finalName;
0744     for (/*int number = 2*/;; ++number) { // TODO: FIXME: If overflow ???
0745         finalName = fileName + '-' + QString::number(number) + extension;
0746         fullName = destFolder + finalName;
0747         dir = QDir(fullName);
0748         if (!dir.exists(fullName))
0749             break;
0750     }
0751 
0752     return finalName;
0753 }
0754 
0755 qint64 Tools::computeSizeRecursively(const QString &path)
0756 {
0757     qint64 result = 0;
0758 
0759     QFileInfo file(path);
0760     result += file.size();
0761     if (file.isDir()) {
0762         QFileInfoList children = QDir(path).entryInfoList(QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden);
0763         Q_FOREACH (const QFileInfo &child, children)
0764             result += computeSizeRecursively(child.absoluteFilePath());
0765     }
0766     return result;
0767 }
0768 
0769 // TODO: Move it from NoteFactory
0770 /*QString NoteFactory::iconForURL(const QUrl &url)
0771 {
0772     QString icon = KMimeType::iconNameForUrl(url.url());
0773     if ( url.scheme() == "mailto" )
0774         icon = "message";
0775     return icon;
0776 }*/
0777 
0778 bool Tools::isAFileCut(const QMimeData *source)
0779 {
0780     if (source->hasFormat(QStringLiteral("application/x-kde-cutselection"))) {
0781         QByteArray array = source->data(QStringLiteral("application/x-kde-cutselection"));
0782         return !array.isEmpty() && QByteArray(array.data(), array.size() + 1).at(0) == '1';
0783     }
0784 
0785     return false;
0786 }
0787 
0788 void Tools::printChildren(QObject *parent)
0789 {
0790     for (const auto& obj : parent->children()) {
0791         qDebug() << Q_FUNC_INFO << obj->metaObject()->className() << ": " << obj->objectName() << Qt::endl;
0792     }
0793 }
0794 
0795 QString Tools::makeStandardCaption(const QString &userCaption)
0796 {
0797     QString caption = QGuiApplication::applicationDisplayName();
0798 
0799     if (!userCaption.isEmpty()) {
0800         return userCaption + i18nc("Document/application separator in titlebar", " – ") + caption;
0801     }
0802 
0803     return caption;
0804 }
0805 
0806 QByteArray Tools::systemCodeset()
0807 {
0808     QByteArray codeset;
0809 #if HAVE_LANGINFO_H
0810     // Qt since 4.2 always returns 'System' as codecForLocale and libraries like for example
0811     // KEncodingFileDialog expects real encoding name. So on systems that have langinfo.h use
0812     // nl_langinfo instead, just like Qt compiled without iconv does. Windows already has its own
0813     // workaround
0814 
0815     codeset = nl_langinfo(CODESET);
0816 
0817     if ((codeset == "ANSI_X3.4-1968") || (codeset == "US-ASCII")) {
0818         // means ascii, "C"; QTextCodec doesn't know, so avoid warning
0819         codeset = "ISO-8859-1";
0820     }
0821 #endif
0822     return codeset;
0823 }