File indexing completed on 2024-04-21 05:48:28
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 == " ") 0076 return "<p> </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 é 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(">", ">"); 0370 text.replace("<", "<"); 0371 text.replace(""", "\""); 0372 text.replace(" ", " "); 0373 text.replace("&", "&"); // 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 }