1 /***************************************************************************
2 * Copyright (C) 2003 by Sébastien Laoût *
3 * slaout@linux62.org *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
20
21 #include "tools.h"
22
23 #include <QtCore/QString>
24 #include <QtCore/QRegExp>
25 #include <QtCore/QList>
26 #include <QtCore/QFileInfo>
27 #include <QtCore/QDir>
28 #include <QtCore/QMimeData>
29 #include <QtCore/QObject>
30 #include <QtCore/QTime>
31 #include <QtGui/QPixmap>
32 #include <QtGui/QImage>
33 #include <QtGui/QFont>
34 #include <QtGui/QFontInfo>
35 #include <QGuiApplication>
36 #include <QDebug>
37
38 #include <QtGui/QTextDocument>
39 #include <QTextBlock>
40
41 #include <KLocalizedString>
42 #include <KIO/CopyJob> //For KIO::trash
43
44 #include "debugwindow.h"
45 #include "config.h"
46
47 //cross reference
48 #include "global.h"
49 #include "bnpview.h"
50 #include "htmlexporter.h"
51 #include "linklabel.h"
52
53 #include <langinfo.h>
54
55 #ifdef HAVE_BALOO
56 #include "nepomukintegration.h"
57 #endif
58
59 QVector<QTime> StopWatch::starts;
60 QVector<double> StopWatch::totals;
61 QVector<uint> StopWatch::counts;
62
start(int id)63 void StopWatch::start(int id)
64 {
65 if (id >= starts.size()) {
66 totals.resize(id + 1);
67 counts.resize(id + 1);
68 for (int i = starts.size(); i <= id; i++) {
69 totals[i] = 0;
70 counts[i] = 0;
71 }
72 starts.resize(id + 1);
73 }
74 starts[id] = QTime::currentTime();
75 }
76
check(int id)77 void StopWatch::check(int id)
78 {
79 if (id >= starts.size())
80 return;
81 double time = starts[id].msecsTo(QTime::currentTime()) / 1000.0;
82 totals[id] += time;
83 counts[id]++;
84 qDebug() << Q_FUNC_INFO << "Timer_" << id << ": " << time << " s [" << counts[id] << " times, total: " << totals[id] << " s, average: " << totals[id] / counts[id] << " s]" << endl;
85 }
86
87
88 /** @namespace HTM
89 * @brief HTML tags constants */
90 namespace HTM {
91 static const char* BR = "<br/>";
92 static const char* PAR = "<p>";
93 static const char* _PAR = "</p>";
94
95 //Styles
96 static const char* FONT_FAMILY = "font-family: %1; ";
97 static const char* FONT_STYLE = "font-style: %1; ";
98 static const char* TEXT_DECORATION = "text-decoration: %1; ";
99
100 static const char* ITALIC = "italic";
101 static const char* UNDERLINE = "underline";
102 static const char* LINE_THROUGH = "line-through";
103
104 //static const char* FONT_WEIGHT = "font-weight: %1; ";
105 static const char* FONT_SIZE = "font-size: %1pt; ";
106 static const char* COLOR = "color: %1; ";
107 }
108
textToHTML(const QString & text)109 QString Tools::textToHTML(const QString &text)
110 {
111 if (text.isEmpty())
112 return "<p></p>";
113 if (/*text.isEmpty() ||*/ text == " " || text == " ")
114 return "<p> </p>";
115
116 // convertFromPlainText() replace "\n\n" by "</p>\n<p>": we don't want that
117 QString htmlString = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
118 return htmlString.replace("</p>\n", "<br>\n<br>\n").replace("\n<p>", "\n"); // Don't replace first and last tags
119 }
120
textToHTMLWithoutP(const QString & text)121 QString Tools::textToHTMLWithoutP(const QString &text)
122 {
123 // textToHTML(text) return "<p>HTMLizedText</p>". We remove the strating "<p>" and ending </p>"
124 QString HTMLizedText = textToHTML(text);
125 return HTMLizedText.mid(3, HTMLizedText.length() - 3 - 4);
126 }
127
htmlToParagraph(const QString & html)128 QString Tools::htmlToParagraph(const QString &html)
129 {
130 QString result = html;
131 bool startedBySpan = false;
132
133 // Remove the <html> start tag, all the <head> and the <body> start
134 // Because <body> can contain style="..." parameter, we transform it to <span>
135 int pos = result.indexOf("<body");
136 if (pos != -1) {
137 result = "<span" + result.mid(pos + 5);
138 startedBySpan = true;
139 }
140
141 // Remove the ending "</p>\n</body></html>", each tag can be separated by space characters (%s)
142 // "</p>" can be omitted (eg. if the HTML doesn't contain paragraph but tables), as well as "</body>" (optinal)
143 pos = result.indexOf(QRegExp("(?:(?:</p>[\\s\\n\\r\\t]*)*</body>[\\s\\n\\r\\t]*)*</html>", Qt::CaseInsensitive));
144 if (pos != -1)
145 result = result.left(pos);
146
147 if (startedBySpan)
148 result += "</span>";
149
150 return result;
151 }
152
153 // The following is adapted from KStringHanlder::tagURLs
154 // The adaptation lies in the change to urlEx
155 // Thanks to Richard Heck
tagURLs(const QString & text)156 QString Tools::tagURLs(const QString &text)
157 {
158 QRegExp urlEx("<!DOCTYPE[^\"]+\"([^\"]+)\"[^\"]+\"([^\"]+)/([^/]+)\\.dtd\">");
159 QString richText(text);
160 int urlPos = 0;
161 int urlLen;
162 if ((urlPos = urlEx.indexIn(richText, urlPos)) >= 0)
163 urlPos += urlEx.matchedLength();
164 else
165 urlPos = 0;
166 urlEx.setPattern("(www\\.(?!\\.)|(fish|(f|ht)tp(|s))://)[\\d\\w\\./,:_~\\?=&;#@\\-\\+\\%\\$]+[\\d\\w/]");
167 while ((urlPos = urlEx.indexIn(richText, urlPos)) >= 0) {
168 urlLen = urlEx.matchedLength();
169
170 //if this match is already a link don't convert it.
171 if(richText.mid(urlPos - 6, 6) == "href=\"") {
172 urlPos += urlLen;
173 continue;
174 }
175
176 QString href = richText.mid(urlPos, urlLen);
177 //we handle basket links separately...
178 if(href.contains("basket://")) {
179 urlPos += urlLen;
180 continue;
181 }
182 // Qt doesn't support (?<=pattern) so we do it here
183 if ((urlPos > 0) && richText[urlPos-1].isLetterOrNumber()) {
184 urlPos++;
185 continue;
186 }
187 // Don't use QString::arg since %01, %20, etc could be in the string
188 QString anchor = "<a href=\"" + href + "\">" + href + "</a>";
189 richText.replace(urlPos, urlLen, anchor);
190 urlPos += anchor.length();
191 }
192 return richText;
193 }
194
tagCrossReferences(const QString & text,bool userLink,HTMLExporter * exporter)195 QString Tools::tagCrossReferences(const QString &text, bool userLink, HTMLExporter *exporter)
196 {
197 QString richText(text);
198
199 int urlPos = 0;
200 int urlLen;
201
202 QRegExp urlEx("\\[\\[(.+)\\]\\]");
203 urlEx.setMinimal(true);
204 while ((urlPos = urlEx.indexIn(richText, urlPos)) >= 0) {
205 urlLen = urlEx.matchedLength();
206 QString href = urlEx.cap(1);
207
208 QStringList hrefParts = href.split('|');
209 QString anchor;
210
211 if(exporter) // if we're exporting this basket to html.
212 anchor = crossReferenceForHtml(hrefParts, exporter);
213 else if(userLink) //the link is manually created (ie [[/top level/sub]] )
214 anchor = crossReferenceForConversion(hrefParts);
215 else // otherwise it's a standard link (ie. [[basket://basket107]] )
216 anchor = crossReferenceForBasket(hrefParts);
217
218
219 richText.replace(urlPos, urlLen, anchor);
220 urlPos += anchor.length();
221 }
222 return richText;
223 }
224
225
crossReferenceForBasket(QStringList linkParts)226 QString Tools::crossReferenceForBasket(QStringList linkParts)
227 {
228 QString basketLink = linkParts.first();
229 QString title;
230
231 bool linkIsEmpty = false;
232
233 if(basketLink == "basket://" || basketLink.isEmpty())
234 linkIsEmpty = true;
235
236 title = linkParts.last().trimmed();
237
238 QString css = LinkLook::crossReferenceLook->toCSS("cross_reference", QColor());
239 QString classes = "cross_reference";
240 classes += (linkIsEmpty ? " xref_empty" : "");
241
242 css += (linkIsEmpty ?
243 " a.xref_empty { display: block; width: 100%; text-decoration: underline; color: #CC2200; }"
244 " a:hover.xref_empty { color: #A55858; }"
245 : "");
246
247 QString anchor = "<style>" + css + "</style><a href=\"" + basketLink + "\" class=\"" + classes + "\">"
248 + QUrl::fromPercentEncoding(title.toUtf8()) + "</a>";
249 return anchor;
250 }
251
crossReferenceForHtml(QStringList linkParts,HTMLExporter * exporter)252 QString Tools::crossReferenceForHtml(QStringList linkParts, HTMLExporter *exporter)
253 {
254 QString basketLink = linkParts.first();
255 QString title;
256
257 bool linkIsEmpty = false;
258
259 if(basketLink == "basket://" || basketLink.isEmpty())
260 linkIsEmpty = true;
261
262 title = linkParts.last().trimmed();
263
264 QString url;
265 if(basketLink.startsWith(QLatin1String("basket://")))
266 url = basketLink.mid(9, basketLink.length() - 9);
267
268 BasketScene *basket = Global::bnpView->basketForFolderName(url);
269
270 //remove the trailing slash.
271 url = url.left(url.length() - 1);
272
273 //if the basket we're trying to link to is the basket that was exported then
274 //we have to use a special way to refer to it for the links.
275 if(basket == exporter->exportedBasket)
276 url = "../../" + exporter->fileName;
277 else {
278 //if we're in the exported basket then the links have to include
279 // the sub directories.
280 if(exporter->currentBasket == exporter->exportedBasket)
281 url.prepend(exporter->basketsFolderName);
282 if(!url.isEmpty())
283 url.append(".html");
284 }
285
286 QString classes = "cross_reference";
287 classes += (linkIsEmpty ? " xref_empty" : "");
288
289 QString css = (linkIsEmpty ?
290 " a.xref_empty { display: block; width: 100%; text-decoration: underline; color: #CC2200; }"
291 " a:hover.xref_empty { color: #A55858; }"
292 : "");
293
294 QString anchor = "<style>" + css + "</style><a href=\"" + url + "\" class=\"" + classes + "\">" + QUrl::fromPercentEncoding(title.toUtf8()) + "</a>";
295 return anchor;
296 }
297
crossReferenceForConversion(QStringList linkParts)298 QString Tools::crossReferenceForConversion(QStringList linkParts)
299 {
300 QString basketLink = linkParts.first();
301 QString title;
302
303 if(basketLink.startsWith(QLatin1String("basket://")))
304 return QString("[[%1|%2]]").arg(basketLink, linkParts.last());
305
306 if(basketLink.endsWith('/'))
307 basketLink = basketLink.left(basketLink.length() - 1);
308
309 QStringList pages = basketLink.split('/');
310
311 if(linkParts.count()<= 1)
312 title = pages.last();
313 else
314 title = linkParts.last().trimmed();
315
316 QString url = Global::bnpView->folderFromBasketNameLink(pages);
317
318 url.prepend("basket://");
319 QString anchor;
320
321 //if we don't change the link return it back exactly
322 //as it came in because it may not be a link.
323 if(url == "basket://" || url.isEmpty()) {
324 QString returnValue = "";
325 foreach(QString s, linkParts)
326 returnValue.append(s);
327 anchor = returnValue.prepend("[[").append("]]");
328 } else
329 anchor = QString("[[%1|%2]]").arg(url, title);
330
331 return anchor;
332 }
333
htmlToText(const QString & html)334 QString Tools::htmlToText(const QString &html)
335 {
336 QString text = htmlToParagraph(html);
337 text.remove("\n");
338 text.replace("</h1>", "\n");
339 text.replace("</h2>", "\n");
340 text.replace("</h3>", "\n");
341 text.replace("</h4>", "\n");
342 text.replace("</h5>", "\n");
343 text.replace("</h6>", "\n");
344 text.replace("</li>", "\n");
345 text.replace("</dt>", "\n");
346 text.replace("</dd>", "\n");
347 text.replace("<dd>", " ");
348 text.replace("</div>", "\n");
349 text.replace("</blockquote>", "\n");
350 text.replace("</caption>", "\n");
351 text.replace("</tr>", "\n");
352 text.replace("</th>", " ");
353 text.replace("</td>", " ");
354 text.replace("<br>", "\n");
355 text.replace("<br />", "\n");
356 text.replace("</p>", "\n");
357 // FIXME: Format <table> tags better, if possible
358 // TODO: Replace é and co. by theire equivalent!
359
360 // To manage tags:
361 int pos = 0;
362 int pos2;
363 QString tag, tag3;
364 // To manage lists:
365 int deep = 0; // The deep of the current line in imbriqued lists
366 QList<bool> ul; // true if current list is a <ul> one, false if it's an <ol> one
367 QList<int> lines; // The line number if it is an <ol> list
368 // We're removing every other tags, or replace them in the case of li:
369 while ((pos = text.indexOf("<"), pos) != -1) {
370 // What is the current tag?
371 tag = text.mid(pos + 1, 2);
372 tag3 = text.mid(pos + 1, 3);
373 // Lists work:
374 if (tag == "ul") {
375 deep++;
376 ul.push_back(true);
377 lines.push_back(-1);
378 } else if (tag == "ol") {
379 deep++;
380 ul.push_back(false);
381 lines.push_back(0);
382 } else if (tag3 == "/ul" || tag3 == "/ol") {
383 deep--;
384 ul.pop_back();
385 lines.pop_back();
386 }
387 // Where the tag closes?
388 pos2 = text.indexOf(">");
389 if (pos2 != -1) {
390 // Remove the tag:
391 text.remove(pos, pos2 - pos + 1);
392 // And replace li with "* ", "x. "... without forbidding to indent that:
393 if (tag == "li") {
394 // How many spaces before the line (indentation):
395 QString spaces = "";
396 for (int i = 1; i < deep; i++)
397 spaces += " ";
398 // The bullet or number of the line:
399 QString bullet = "* ";
400 if (ul.back() == false) {
401 lines.push_back(lines.back() + 1);
402 lines.pop_back();
403 bullet = QString::number(lines.back()) + ". ";
404 }
405 // Insertion:
406 text.insert(pos, spaces + bullet);
407 }
408 if ((tag3 == "/ul" || tag3 == "/ol") && deep == 0)
409 text.insert(pos, "\n"); // Empty line before and after a set of lists
410 }
411 ++pos;
412 }
413
414 text.replace(">", ">");
415 text.replace("<", "<");
416 text.replace(""", "\"");
417 text.replace(" ", " ");
418 text.replace("&", "&"); // CONVERT IN LAST!!
419
420 // HtmlContent produces "\n" for empty note
421 if (text == "\n")
422 text = "";
423
424 return text;
425 }
426
textDocumentToMinimalHTML(QTextDocument * document)427 QString Tools::textDocumentToMinimalHTML(QTextDocument* document) {
428
429 QString result =
430 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n"
431 "<html><head><meta name=\"qrichtext\" content=\"1\" /><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><style type=\"text/css\">\n"
432 "p, li { white-space: pre-wrap; margin: 0px; }\n"
433 "</style></head><body>\n";
434 QFont defaultFont;
435 int fragCount, blockCount = 0;
436 bool leadingBrNeeded = false;
437
438 for (QTextBlock blockIt = document->begin(); blockIt != document->end(); blockIt = blockIt.next(), ++blockCount) {
439
440 result += HTM::PAR;
441
442 // Prepare to detect empty blocks
443 fragCount = 0;
444
445 for (QTextBlock::iterator subIt = blockIt.begin(); !(subIt.atEnd()); ++subIt, ++fragCount) {
446 QTextFragment currentFragment = subIt.fragment();
447
448 if (currentFragment.isValid()) {
449
450 // Dealing with need to add leading linebreak (see later for
451 // further notes)
452 if (leadingBrNeeded)
453 {
454 result += HTM::BR;
455 leadingBrNeeded = false;
456 }
457
458 QTextCharFormat charFmt = currentFragment.charFormat();
459 const QColor& textColor = charFmt.foreground().color();
460 bool isTextBlack = (textColor == QColor() || textColor == QColor(Qt::black));
461
462 if (charFmt.font() == defaultFont && isTextBlack) {
463 result += currentFragment.text().toHtmlEscaped();
464 continue;
465 }
466
467 //If we use charFmt.fontWeight, setting a tag overrides it and all characters become non-bold.
468 //So we use <b> </b> instead
469 bool bold = (charFmt.fontWeight() >= QFont::Bold);
470 if (bold)
471 result += "<b>";
472
473 //Compose style string (font and color)
474 result += "<span style=\"";
475
476 if (charFmt.fontFamily() != defaultFont.family() && !charFmt.fontFamily().isEmpty())
477 result += QString(HTM::FONT_FAMILY).arg(charFmt.fontFamily());
478
479
480 if (charFmt.fontItalic())
481 result += QString(HTM::FONT_STYLE).arg(HTM::ITALIC);
482 if (charFmt.fontUnderline())
483 result += QString(HTM::TEXT_DECORATION).arg(HTM::UNDERLINE);
484 if (charFmt.fontStrikeOut())
485 result += QString(HTM::TEXT_DECORATION).arg(HTM::LINE_THROUGH);
486
487
488 /*if (charFmt.fontWeight() != defaultFont.weight()) {
489 QFont::Weight weight = (charFmt.fontWeight() >= QFont::Bold) ? QFont::Bold : QFont::Normal;
490 result += QString(HTM::FONT_WEIGHT).arg(weight);
491 }*/
492
493 if (charFmt.fontPointSize() != defaultFont.pointSize() && charFmt.fontPointSize() != 0)
494 result += QString(HTM::FONT_SIZE).arg(charFmt.fontPointSize());
495
496 if (!isTextBlack)
497 result += QString(HTM::COLOR).arg(textColor.name());
498
499
500 result += "\">" + currentFragment.text().toHtmlEscaped() + "</span>";
501
502 if (bold)
503 result += "</b>";
504 }
505 }
506
507 // Detecting empty blocks (Qt4 fails to generate a fragment from an empty line)
508 // Inserting a linebreak directly here seems to cause the renderer to render
509 // two breaks, so have to append it to the contents of the previous paragraph...
510 if (!fragCount)
511 {
512 // If the first fragment is an empty fragment, the linebreak must be
513 // added to the next fragment otherwise you get the above double breaks
514 if(!blockCount) leadingBrNeeded = true;
515
516 // Deal with the problem only when the last block is not affected,
517 // otherwise you get double breaks again... Blocks counted from 0
518 else if (blockCount != (document->blockCount() - 1))
519 {
520 result.chop(7);
521 result = result + HTM::BR + HTM::_PAR + HTM::PAR;
522 }
523 }
524
525 result += HTM::_PAR;
526 }
527
528 result += "</body></html>";
529 return result;
530 }
531
cssFontDefinition(const QFont & font,bool onlyFontFamily)532 QString Tools::cssFontDefinition(const QFont &font, bool onlyFontFamily)
533 {
534 // The font definition:
535 QString definition = QString(font.italic() ? "italic " : "") +
536 QString(font.bold() ? "bold " : "") +
537 QString::number(QFontInfo(font).pixelSize()) + "px ";
538
539 // Then, try to match the font name with a standard CSS font family:
540 QString genericFont = "";
541 if (definition.contains("serif", Qt::CaseInsensitive) || definition.contains("roman", Qt::CaseInsensitive))
542 genericFont = "serif";
543 // No "else if" because "sans serif" must be counted as "sans". So, the order between "serif" and "sans" is important
544 if (definition.contains("sans", Qt::CaseInsensitive) || definition.contains("arial", Qt::CaseInsensitive) || definition.contains("helvetica", Qt::CaseInsensitive))
545 genericFont = "sans-serif";
546 if (definition.contains("mono", Qt::CaseInsensitive) || definition.contains("courier", Qt::CaseInsensitive) ||
547 definition.contains("typewriter", Qt::CaseInsensitive) || definition.contains("console", Qt::CaseInsensitive) ||
548 definition.contains("terminal", Qt::CaseInsensitive) || definition.contains("news", Qt::CaseInsensitive))
549 genericFont = "monospace";
550
551 // Eventually add the generic font family to the definition:
552 QString fontDefinition = "\"" + font.family() + "\"";
553 if (!genericFont.isEmpty())
554 fontDefinition += ", " + genericFont;
555
556 if (onlyFontFamily)
557 return fontDefinition;
558
559 return definition + fontDefinition;
560 }
561
stripEndWhiteSpaces(const QString & string)562 QString Tools::stripEndWhiteSpaces(const QString &string)
563 {
564 uint length = string.length();
565 uint i;
566 for (i = length; i > 0; --i)
567 if (!string[i-1].isSpace())
568 break;
569 if (i == 0)
570 return "";
571 else
572 return string.left(i);
573 }
574
575
576
isWebColor(const QColor & color)577 bool Tools::isWebColor(const QColor &color)
578 {
579 int r = color.red(); // The 216 web colors are those colors whose RGB (Red, Green, Blue)
580 int g = color.green(); // values are all in the set (0, 51, 102, 153, 204, 255).
581 int b = color.blue();
582
583 return ((r == 0 || r == 51 || r == 102 ||
584 r == 153 || r == 204 || r == 255) &&
585 (g == 0 || g == 51 || g == 102 ||
586 g == 153 || g == 204 || g == 255) &&
587 (b == 0 || b == 51 || b == 102 ||
588 b == 153 || b == 204 || b == 255));
589 }
590
mixColor(const QColor & color1,const QColor & color2)591 QColor Tools::mixColor(const QColor &color1, const QColor &color2)
592 {
593 QColor mixedColor;
594 mixedColor.setRgb((color1.red() + color2.red()) / 2,
595 (color1.green() + color2.green()) / 2,
596 (color1.blue() + color2.blue()) / 2);
597 return mixedColor;
598 }
599
tooDark(const QColor & color)600 bool Tools::tooDark(const QColor &color)
601 {
602 return color.value() < 175;
603 }
604
605
606 // TODO: Use it for all indentPixmap()
normalizePixmap(const QPixmap & pixmap,int width,int height)607 QPixmap Tools::normalizePixmap(const QPixmap &pixmap, int width, int height)
608 {
609 if (height <= 0)
610 height = width;
611
612 if (pixmap.isNull() || (pixmap.width() == width && pixmap.height() == height))
613 return pixmap;
614
615 return pixmap;
616 }
617
indentPixmap(const QPixmap & source,int depth,int deltaX)618 QPixmap Tools::indentPixmap(const QPixmap &source, int depth, int deltaX)
619 {
620 // Verify if it is possible:
621 if (depth <= 0 || source.isNull())
622 return source;
623
624 // Compute the number of pixels to indent:
625 if (deltaX <= 0)
626 deltaX = 2 * source.width() / 3;
627 int indent = depth * deltaX;
628
629 // Create the images:
630 QImage resultImage(indent + source.width(), source.height(), QImage::Format_ARGB32);
631 resultImage.setColorCount(32);
632
633 QImage sourceImage = source.toImage();
634
635 // Clear the indent part (the left part) by making it fully transparent:
636 uint *p;
637 for (int row = 0; row < resultImage.height(); ++row) {
638 for (int column = 0; column < resultImage.width(); ++column) {
639 p = (uint *)resultImage.scanLine(row) + column;
640 *p = 0; // qRgba(0, 0, 0, 0)
641 }
642 }
643
644 // Copy the source image byte per byte to the right part:
645 uint *q;
646 for (int row = 0; row < sourceImage.height(); ++row) {
647 for (int column = 0; column < sourceImage.width(); ++column) {
648 p = (uint *)resultImage.scanLine(row) + indent + column;
649 q = (uint *)sourceImage.scanLine(row) + column;
650 *p = *q;
651 }
652 }
653
654 // And return the result:
655 QPixmap result = QPixmap::fromImage(resultImage);
656 return result;
657 }
658
deleteRecursively(const QString & folderOrFile)659 void Tools::deleteRecursively(const QString &folderOrFile)
660 {
661 if (folderOrFile.isEmpty())
662 return;
663
664 QFileInfo fileInfo(folderOrFile);
665 if (fileInfo.isDir()) {
666 // Delete the child files:
667 QDir dir(folderOrFile, QString::null, QDir::Name | QDir::IgnoreCase, QDir::TypeMask | QDir::Hidden);
668 QStringList list = dir.entryList();
669 for (QStringList::Iterator it = list.begin(); it != list.end(); ++it)
670 if (*it != "." && *it != "..")
671 deleteRecursively(folderOrFile + "/" + *it);
672 // And then delete the folder:
673 dir.rmdir(folderOrFile);
674 } else
675 // Delete the file:
676 QFile::remove(folderOrFile);
677 #ifdef HAVE_BALOO
678 //The file/dir is deleted; now deleting the Metadata in Nepomuk
679 DEBUG_WIN << "NepomukIntegration: Deleting File[" + folderOrFile + "]:"; // <font color=red>Updating Metadata</font>!";
680 nepomukIntegration::deleteMetadata(folderOrFile);
681 #endif
682 }
683
deleteMetadataRecursively(const QString & folderOrFile)684 void Tools::deleteMetadataRecursively(const QString &folderOrFile)
685 {
686 QFileInfo fileInfo(folderOrFile);
687 if (fileInfo.isDir()) {
688 // Delete Metadata of the child files:
689 QDir dir(folderOrFile, QString::null, QDir::Name | QDir::IgnoreCase, QDir::TypeMask | QDir::Hidden);
690 QStringList list = dir.entryList();
691 for (QStringList::Iterator it = list.begin(); it != list.end(); ++it)
692 if (*it != "." && *it != "..")
693 deleteMetadataRecursively(folderOrFile + "/" + *it);
694 }
695 #ifdef HAVE_BALOO
696 DEBUG_WIN << "NepomukIntegration: Deleting File[" + folderOrFile + "]:"; // <font color=red>Updating Metadata</font>!";
697 nepomukIntegration::deleteMetadata(folderOrFile);
698 #endif
699 }
700
trashRecursively(const QString & folderOrFile)701 void Tools::trashRecursively(const QString &folderOrFile)
702 {
703 if (folderOrFile.isEmpty())
704 return;
705
706 #ifdef HAVE_BALOO
707 //First, deleting the Metadata in Nepomuk
708 deleteMetadataRecursively(folderOrFile);
709 #endif
710
711 KIO::trash( QUrl::fromLocalFile(folderOrFile), KIO::HideProgressInfo );
712 }
713
714
fileNameForNewFile(const QString & wantedName,const QString & destFolder)715 QString Tools::fileNameForNewFile(const QString &wantedName, const QString &destFolder)
716 {
717 QString fileName = wantedName;
718 QString fullName = destFolder + fileName;
719 QString extension = "";
720 int number = 2;
721 QDir dir;
722
723 // First check if the file do not exists yet (simplier and more often case)
724 dir = QDir(fullName);
725 if (! dir.exists(fullName))
726 return fileName;
727
728 // Find the file extension, if it exists : Split fileName in fileName and extension
729 // Example : fileName == "note5-3.txt" => fileName = "note5-3" and extension = ".txt"
730 int extIndex = fileName.lastIndexOf('.');
731 if (extIndex != -1 && extIndex != int(fileName.length() - 1)) { // Extension found and fileName do not ends with '.' !
732 extension = fileName.mid(extIndex);
733 fileName.truncate(extIndex);
734 } // else fileName = fileName and extension = ""
735
736 // Find the file number, if it exists : Split fileName in fileName and number
737 // Example : fileName == "note5-3" => fileName = "note5" and number = 3
738 int extNumber = fileName.lastIndexOf('-');
739 if (extNumber != -1 && extNumber != int(fileName.length() - 1)) { // Number found and fileName do not ends with '-' !
740 bool isANumber;
741 int theNumber = fileName.mid(extNumber + 1).toInt(&isANumber);
742 if (isANumber) {
743 number = theNumber;
744 fileName.truncate(extNumber);
745 } // else :
746 } // else fileName = fileName and number = 2 (because if the file already exists, the genereated name is at last the 2nd)
747
748 QString finalName;
749 for (/*int number = 2*/; ; ++number) { // TODO: FIXME: If overflow ???
750 finalName = fileName + "-" + QString::number(number) + extension;
751 fullName = destFolder + finalName;
752 dir = QDir(fullName);
753 if (! dir.exists(fullName))
754 break;
755 }
756
757 return finalName;
758 }
759
computeSizeRecursively(const QString & path)760 qint64 Tools::computeSizeRecursively(const QString& path)
761 {
762 qint64 result = 0;
763
764 QFileInfo file(path);
765 result += file.size();
766 if (file.isDir()) {
767 QFileInfoList children = QDir(path).entryInfoList(QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden);
768 foreach (const QFileInfo& child, children)
769 result += computeSizeRecursively(child.absoluteFilePath());
770 }
771 return result;
772 }
773
774 // TODO: Move it from NoteFactory
775 /*QString NoteFactory::iconForURL(const QUrl &url)
776 {
777 QString icon = KMimeType::iconNameForUrl(url.url());
778 if ( url.scheme() == "mailto" )
779 icon = "message";
780 return icon;
781 }*/
782
isAFileCut(const QMimeData * source)783 bool Tools::isAFileCut(const QMimeData *source)
784 {
785 if (source->hasFormat("application/x-kde-cutselection")) {
786 QByteArray array = source->data("application/x-kde-cutselection");
787 return !array.isEmpty() && QByteArray(array.data(), array.size() + 1).at(0) == '1';
788 } else
789 return false;
790 }
791
printChildren(QObject * parent)792 void Tools::printChildren(QObject* parent)
793 {
794 const QObjectList objs = parent->children();
795 QObject * obj;
796 for (int i = 0 ; i < objs.size() ; i++) {
797 obj = objs.at(i);
798 qDebug() << Q_FUNC_INFO << obj->metaObject()->className() << ": " << obj->objectName() << endl;
799 }
800
801 }
802
makeStandardCaption(const QString & userCaption)803 QString Tools::makeStandardCaption(const QString& userCaption)
804 {
805 QString caption = QGuiApplication::applicationDisplayName();
806
807 if (!userCaption.isEmpty())
808 return userCaption + i18nc("Document/application separator in titlebar", " – ") + caption; else
809 return caption;
810 }
811
812
systemCodeset()813 QByteArray Tools::systemCodeset()
814 {
815 QByteArray codeset;
816 #if HAVE_LANGINFO_H
817 // Qt since 4.2 always returns 'System' as codecForLocale and libraries like for example
818 // KEncodingFileDialog expects real encoding name. So on systems that have langinfo.h use
819 // nl_langinfo instead, just like Qt compiled without iconv does. Windows already has its own
820 // workaround
821
822 codeset = nl_langinfo(CODESET);
823
824 if ((codeset == "ANSI_X3.4-1968") || (codeset == "US-ASCII")) {
825 // means ascii, "C"; QTextCodec doesn't know, so avoid warning
826 codeset = "ISO-8859-1";
827 }
828 #endif
829 return codeset;
830 }
831