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 == "&nbsp;")
114         return "<p>&nbsp;</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 &eacute; 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("&gt;",   ">");
415     text.replace("&lt;",   "<");
416     text.replace("&quot;", "\"");
417     text.replace("&nbsp;", " ");
418     text.replace("&amp;",  "&"); // 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