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 "htmlexporter.h"
22 
23 #include "bnpview.h"
24 #include "basketlistview.h"
25 #include "basketscene.h"
26 #include "note.h"
27 #include "tools.h"
28 #include "config.h"
29 #include "linklabel.h"
30 #include "notecontent.h"
31 
32 #include <KAboutData>
33 #include <KConfig>
34 #include <KConfigGroup>
35 #include <KMessageBox>
36 #include <KLocalizedString>
37 #include <KIconLoader>
38 
39 #include <KIO/Job>      //For KIO::file_copy
40 #include <KIO/CopyJob>  //For KIO::copy
41 
42 #include <QtCore/QDir>
43 #include <QtCore/QFile>
44 #include <QtCore/QTextStream>
45 #include <QtCore/QList>
46 #include <QtGui/QPainter>
47 #include <QtGui/QPixmap>
48 #include <QApplication>
49 #include <QFileDialog>
50 #include <QLocale>
51 #include <QProgressDialog>
52 
HTMLExporter(BasketScene * basket)53 HTMLExporter::HTMLExporter(BasketScene *basket) : dialog(new QProgressDialog())
54 {
55     QDir dir;
56 
57     // Compute a default file name & path:
58     KConfigGroup config = Global::config()->group("Export to HTML");
59     QString folder = config.readEntry("lastFolder", QDir::homePath()) + "/";
60     QString url = folder + QString(basket->basketName()).replace("/", "_") + ".html";
61 
62     // Ask a file name & path to the user:
63     QString filter = "*.html *.htm|" + i18n("HTML Documents") + "\n*|" + i18n("All Files");
64     QString destination = url;
65     for (bool askAgain = true; askAgain;) {
66         // Ask:
67         destination = QFileDialog::getSaveFileName(NULL, i18n("Export to HTML"), destination, filter);
68         // User canceled?
69         if (destination.isEmpty())
70             return;
71         // File already existing? Ask for overriding:
72         if (dir.exists(destination)) {
73             int result = KMessageBox::questionYesNoCancel(
74                              0,
75                              "<qt>" + i18n("The file <b>%1</b> already exists. Do you really want to override it?",
76                                            QUrl::fromLocalFile(destination).fileName()),
77                              i18n("Override File?"),
78                              KGuiItem(i18n("&Override"), "document-save")
79                          );
80             if (result == KMessageBox::Cancel)
81                 return;
82             else if (result == KMessageBox::Yes)
83                 askAgain = false;
84         } else
85             askAgain = false;
86     }
87 
88     // Create the progress dialog that will always be shown during the export:
89     dialog->setWindowTitle(i18n("Export to HTML"));
90     dialog->setLabelText(i18n("Exporting to HTML. Please wait..."));
91     dialog->setCancelButton(NULL);
92     dialog->setAutoClose(true);
93     dialog->show();
94 
95     // Remember the last folder used for HTML exporation:
96     config.writeEntry("lastFolder", QUrl::fromLocalFile(destination).adjusted(QUrl::RemoveFilename).path());
97     config.sync();
98 
99     prepareExport(basket, destination);
100     exportBasket(basket, /*isSubBasketScene*/false);
101 
102     dialog->setValue(dialog->value() + 1); // Finishing finished
103 }
104 
~HTMLExporter()105 HTMLExporter::~HTMLExporter()
106 {
107 }
108 
prepareExport(BasketScene * basket,const QString & fullPath)109 void HTMLExporter::prepareExport(BasketScene *basket, const QString &fullPath)
110 {
111     dialog->setRange(0,/*Preparation:*/1 + /*Finishing:*/1 + /*Basket:*/1 + /*SubBaskets:*/Global::bnpView->basketCount(Global::bnpView->listViewItemForBasket(basket)));
112     dialog->setValue(0);
113     qApp->processEvents();
114 
115     // Remember the file path chozen by the user:
116     filePath = fullPath;
117     fileName = QUrl::fromLocalFile(fullPath).fileName();
118     exportedBasket = basket;
119     currentBasket = 0;
120 
121     BasketListViewItem *item = Global::bnpView->listViewItemForBasket(basket);
122     withBasketTree = (item->childCount() >= 0);
123 
124     // Create and empty the files folder:
125     QString filesFolderPath = i18nc("HTML export folder (files)", "%1_files", filePath) + "/"; // eg.: "/home/seb/foo.html_files/"
126     Tools::deleteRecursively(filesFolderPath);
127     QDir dir;
128     dir.mkdir(filesFolderPath);
129 
130     // Create sub-folders:
131     iconsFolderPath   = filesFolderPath + i18nc("HTML export folder (icons)",   "icons")   + "/"; // eg.: "/home/seb/foo.html_files/icons/"
132     imagesFolderPath  = filesFolderPath + i18nc("HTML export folder (images)",  "images")  + "/"; // eg.: "/home/seb/foo.html_files/images/"
133     basketsFolderPath = filesFolderPath + i18nc("HTML export folder (baskets)", "baskets") + "/"; // eg.: "/home/seb/foo.html_files/baskets/"
134     dir.mkdir(iconsFolderPath);
135     dir.mkdir(imagesFolderPath);
136     dir.mkdir(basketsFolderPath);
137 
138     dialog->setValue(dialog->value() + 1); // Preparation finished
139 }
140 
exportBasket(BasketScene * basket,bool isSubBasket)141 void HTMLExporter::exportBasket(BasketScene *basket, bool isSubBasket)
142 {
143     if (!basket->isLoaded()) {
144         basket->load();
145     }
146 
147     currentBasket = basket;
148 
149     // Compute the absolute & relative paths for this basket:
150     filesFolderPath   = i18nc("HTML export folder (files)", "%1_files", filePath) + "/";
151     if (isSubBasket) {
152         basketFilePath    = basketsFolderPath + basket->folderName().left(basket->folderName().length() - 1) + ".html";
153         filesFolderName   = "../";
154         dataFolderName    = basket->folderName().left(basket->folderName().length() - 1) + "-" + i18nc("HTML export folder (data)", "data") + "/";
155         dataFolderPath    = basketsFolderPath + dataFolderName;
156         basketsFolderName = "";
157     } else {
158         basketFilePath    = filePath;
159         filesFolderName   = i18nc("HTML export folder (files)", "%1_files", QUrl::fromLocalFile(filePath).fileName()) + "/";
160         dataFolderName    = filesFolderName + i18nc("HTML export folder (data)",    "data")  + "/";
161         dataFolderPath    = filesFolderPath + i18nc("HTML export folder (data)",    "data")  + "/";
162         basketsFolderName = filesFolderName + i18nc("HTML export folder (baskets)", "baskets")  + "/";
163     }
164     iconsFolderName   = (isSubBasket ? "../" : filesFolderName) + i18nc("HTML export folder (icons)",   "icons")   + "/"; // eg.: "foo.html_files/icons/"   or "../icons/"
165     imagesFolderName  = (isSubBasket ? "../" : filesFolderName) + i18nc("HTML export folder (images)",  "images")  + "/"; // eg.: "foo.html_files/images/"  or "../images/"
166 
167     qDebug() << "Exporting ================================================";
168     qDebug() << "  filePath:" << filePath;
169     qDebug() << "  basketFilePath:" << basketFilePath;
170     qDebug() << "  filesFolderPath:" << filesFolderPath;
171     qDebug() << "  filesFolderName:" << filesFolderName;
172     qDebug() << "  iconsFolderPath:" << iconsFolderPath;
173     qDebug() << "  iconsFolderName:" << iconsFolderName;
174     qDebug() << "  imagesFolderPath:" << imagesFolderPath;
175     qDebug() << "  imagesFolderName:" << imagesFolderName;
176     qDebug() << "  dataFolderPath:" << dataFolderPath;
177     qDebug() << "  dataFolderName:" << dataFolderName;
178     qDebug() << "  basketsFolderPath:" << basketsFolderPath;
179     qDebug() << "  basketsFolderName:" << basketsFolderName;
180 
181     // Create the data folder for this basket:
182     QDir dir;
183     dir.mkdir(dataFolderPath);
184 
185     backgroundColorName = basket->backgroundColor().name().toLower().mid(1);
186 
187     // Generate basket icons:
188     QString basketIcon16 = iconsFolderName + copyIcon(basket->icon(), 16);
189     QString basketIcon32 = iconsFolderName + copyIcon(basket->icon(), 32);
190 
191     // Generate the [+] image for groups:
192     QPixmap expandGroup(Note::EXPANDER_WIDTH, Note::EXPANDER_HEIGHT);
193     expandGroup.fill(basket->backgroundColor());
194     QPainter painter(&expandGroup);
195     Note::drawExpander(&painter, 0, 0, basket->backgroundColor(), /*expand=*/true, basket);
196     painter.end();
197     expandGroup.save(imagesFolderPath + "expand_group_" + backgroundColorName + ".png", "PNG");
198 
199     // Generate the [-] image for groups:
200     QPixmap foldGroup(Note::EXPANDER_WIDTH, Note::EXPANDER_HEIGHT);
201     foldGroup.fill(basket->backgroundColor());
202     painter.begin(&foldGroup);
203     Note::drawExpander(&painter, 0, 0, basket->backgroundColor(), /*expand=*/false, basket);
204     painter.end();
205     foldGroup.save(imagesFolderPath + "fold_group_" + backgroundColorName + ".png", "PNG");
206 
207     // Open the file to write:
208     QFile file(basketFilePath);
209     if (!file.open(QIODevice::WriteOnly))
210         return;
211     stream.setDevice(&file);
212     stream.setCodec("UTF-8");
213 
214     // Compute the colors to draw dragient for notes:
215     QColor topBgColor;
216     QColor bottomBgColor;
217     Note::getGradientColors(basket->backgroundColor(), &topBgColor, &bottomBgColor);
218     // Compute the gradient image for notes:
219     QString gradientImageFileName = BasketScene::saveGradientBackground(basket->backgroundColor(), basket->QGraphicsScene::font(), imagesFolderPath);
220 
221     // Output the header:
222     QString borderColor = Tools::mixColor(basket->backgroundColor(), basket->textColor()).name();
223     stream <<
224     "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n"
225     "<html>\n"
226     " <head>\n"
227     "  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"
228     "  <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"><meta name=\"Generator\" content=\""
229            << QGuiApplication::applicationDisplayName() << " " << VERSION << " " << KAboutData::applicationData().homepage() << "\">\n"
230     "  <style type=\"text/css\">\n"
231 //      "   @media print {\n"
232 //      "    span.printable { display: inline; }\n"
233 //      "   }\n"
234     "   body { margin: 10px; font: 11px sans-serif; }\n" // TODO: Use user font
235     "   h1 { text-align: center; }\n"
236     "   img { border: none; vertical-align: middle; }\n";
237     if (withBasketTree) {
238         stream <<
239         "   .tree { margin: 0; padding: 1px 0 1px 1px; width: 150px; _width: 149px; overflow: hidden; float: left; }\n"
240         "   .tree ul { margin: 0 0 0 10px; padding: 0; }\n"
241         "   .tree li { padding: 0; margin: 0; list-style: none; }\n"
242         "   .tree a { display: block; padding: 1px; height: 16px; text-decoration: none;\n"
243         "             white-space: nowrap; word-wrap: normal; text-wrap: suppress; color: black; }\n"
244         "   .tree span { -moz-border-radius: 6px; display: block; float: left;\n"
245         "                line-height: 16px; height: 16px; vertical-align: middle; padding: 0 1px; }\n"
246         "   .tree img { vertical-align: top; padding-right: 1px; }\n"
247         "   .tree .current { background-color: " << qApp->palette().color(QPalette::Highlight).name() << "; "
248         "-moz-border-radius: 3px 0 0 3px; border-radius: 3px 0 0 3px; color: " << qApp->palette().color(QPalette::Highlight).name() << "; }\n"
249         "   .basketSurrounder { margin-left: 152px; _margin: 0; _float: right; }\n";
250     }
251     stream <<
252     "   .basket { background-color: " << basket->backgroundColor().name() << "; border: solid " << borderColor << " 1px; "
253     "font: " << Tools::cssFontDefinition(basket->QGraphicsScene::font()) << "; color: " << basket->textColor().name() << "; padding: 1px; width: 100%; }\n"
254     "   table.basket { border-collapse: collapse; }\n"
255     "   .basket * { padding: 0; margin: 0; }\n"
256     "   .basket table { width: 100%; border-spacing: 0; _border-collapse: collapse; }\n"
257     "   .column { vertical-align: top; }\n"
258     "   .columnHandle { width: " << Note::RESIZER_WIDTH << "px; background: transparent url('" << imagesFolderName << "column_handle_" << backgroundColorName << ".png') repeat-y; }\n"
259     "   .group { margin: 0; padding: 0; border-collapse: collapse; width: 100% }\n"
260     "   .groupHandle { margin: 0; width: " << Note::GROUP_WIDTH << "px; text-align: center; }\n"
261     "   .note { padding: 1px 2px; background: " << bottomBgColor.name() << " url('" << imagesFolderName << gradientImageFileName << "')"
262     " repeat-x; border-top: solid " << topBgColor.name() <<
263     " 1px; border-bottom: solid " << Tools::mixColor(topBgColor, bottomBgColor).name() <<
264     " 1px; width: 100%; }\n"
265     "   .tags { width: 1px; white-space: nowrap; }\n"
266     "   .tags img { padding-right: 2px; }\n"
267     << LinkLook::soundLook->toCSS("sound", basket->textColor())
268     << LinkLook::fileLook->toCSS("file", basket->textColor())
269     << LinkLook::localLinkLook->toCSS("local", basket->textColor())
270     << LinkLook::networkLinkLook->toCSS("network", basket->textColor())
271     << LinkLook::launcherLook->toCSS("launcher", basket->textColor())
272     << LinkLook::crossReferenceLook->toCSS("cross_reference", basket->textColor())
273     <<
274     "   .unknown { margin: 1px 2px; border: 1px solid " << borderColor << "; -moz-border-radius: 4px; }\n";
275     QList<State*> states = basket->usedStates();
276     QString statesCss;
277     for (State::List::Iterator it = states.begin(); it != states.end(); ++it)
278         statesCss += (*it)->toCSS(imagesFolderPath, imagesFolderName, basket->QGraphicsScene::font());
279     stream <<
280     statesCss <<
281     "   .credits { text-align: right; margin: 3px 0 0 0; _margin-top: -17px; font-size: 80%; color: " << borderColor << "; }\n"
282     "  </style>\n"
283     "  <title>" << Tools::textToHTMLWithoutP(basket->basketName()) << "</title>\n"
284     "  <link rel=\"shortcut icon\" type=\"image/png\" href=\"" << basketIcon16 << "\">\n";
285     // Create the column handle image:
286     QPixmap columnHandle(Note::RESIZER_WIDTH, 50);
287     painter.begin(&columnHandle);
288     Note::drawInactiveResizer(&painter, 0, 0, columnHandle.height(), basket->backgroundColor(), /*column=*/true);
289     painter.end();
290     columnHandle.save(imagesFolderPath + "column_handle_" + backgroundColorName + ".png", "PNG");
291 
292     stream <<
293     " </head>\n"
294     " <body>\n"
295     "  <h1><img src=\"" << basketIcon32 << "\" width=\"32\" height=\"32\" alt=\"\"> " << Tools::textToHTMLWithoutP(basket->basketName()) << "</h1>\n";
296 
297     if (withBasketTree)
298         writeBasketTree(basket);
299 
300     // If filtering, only export filtered notes, inform to the user:
301     // TODO: Filtering tags too!!
302     // TODO: Make sure only filtered notes are exported!
303 //  if (decoration()->filterData().isFiltering)
304 //      stream <<
305 //          "  <p>" << i18n("Notes matching the filter &quot;%1&quot;:", Tools::textToHTMLWithoutP(decoration()->filterData().string)) << "</p>\n";
306 
307     stream <<
308     "  <div class=\"basketSurrounder\">\n";
309 
310     if (basket->isColumnsLayout())
311         stream <<
312         "   <table class=\"basket\">\n"
313         "    <tr>\n";
314     else
315         stream <<
316         "   <div class=\"basket\" style=\"position: relative; height: " << basket->sceneRect().height() << "px; width: " << basket->sceneRect().width() << "px; min-width: 100%;\">\n";
317 
318     for (Note *note = basket->firstNote(); note; note = note->next())
319         exportNote(note, /*indent=*/(basket->isFreeLayout() ? 4 : 5));
320 
321     // Output the footer:
322     if (basket->isColumnsLayout())
323         stream <<
324         "    </tr>\n"
325         "   </table>\n";
326     else
327         stream <<
328         "   </div>\n";
329     stream << QString(
330         "  </div>\n"
331         "  <p class=\"credits\">%1</p>\n").arg(
332             i18n("Made with <a href=\"%1\">%2</a> %3, a tool to take notes and keep information at hand.",
333                  KAboutData::applicationData().homepage(), QGuiApplication::applicationDisplayName(), VERSION));
334 
335     stream <<
336     " </body>\n"
337     "</html>\n";
338 
339     file.close();
340     stream.setDevice(0);
341     dialog->setValue(dialog->value() + 1); // Basket exportation finished
342 
343     // Recursively export child baskets:
344     BasketListViewItem *item = Global::bnpView->listViewItemForBasket(basket);
345     if (item->childCount() >= 0) {
346         for (int i = 0; i < item->childCount(); i++) {
347             exportBasket(((BasketListViewItem *)item->child(i))->basket(), /*isSubBasket=*/true);
348         }
349     }
350 }
351 
exportNote(Note * note,int indent)352 void HTMLExporter::exportNote(Note *note, int indent)
353 {
354     QString spaces;
355 
356     if (note->isColumn()) {
357         QString width = "";
358         if (false/*TODO: DEBUG AND REENABLE: hasResizer()*/) {
359             // As we cannot be precise in CSS (say eg. "width: 50%-40px;"),
360             // we output a percentage that is approximatively correct.
361             // For instance, we compute the currently used percentage of width in the basket
362             // and try make make it the same on a 1024*768 display in a Web browser:
363             int availableSpaceForColumnsInThisBasket = note->basket()->sceneRect().width() - (note->basket()->columnsCount() - 1) * Note::RESIZER_WIDTH;
364             int availableSpaceForColumnsInBrowser    = 1024    /* typical screen width */
365                     - 25    /* window border and scrollbar width */
366                     - 2 * 5 /* page margin */
367                     - (note->basket()->columnsCount() - 1) * Note::RESIZER_WIDTH;
368             if (availableSpaceForColumnsInThisBasket <= 0)
369                 availableSpaceForColumnsInThisBasket = 1;
370             int widthValue = (int)(availableSpaceForColumnsInBrowser * (double) note->groupWidth() / availableSpaceForColumnsInThisBasket);
371             if (widthValue <= 0)
372                 widthValue = 1;
373             if (widthValue > 100)
374                 widthValue = 100;
375             width = QString(" width=\"%1%\"").arg(QString::number(widthValue));
376         }
377         stream << spaces.fill(' ', indent) << "<td class=\"column\"" << width << ">\n";
378 
379         // Export child notes:
380         for (Note *child = note->firstChild(); child; child = child->next()) {
381             stream << spaces.fill(' ', indent + 1);
382             exportNote(child, indent + 1);
383             stream << '\n';
384         }
385 
386         stream << spaces.fill(' ', indent) << "</td>\n";
387         if (note->hasResizer())
388             stream << spaces.fill(' ', indent) << "<td class=\"columnHandle\"></td>\n";
389         return;
390     }
391 
392     QString freeStyle;
393     if (note->isFree())
394         freeStyle = " style=\"position: absolute; left: " + QString::number(note->x()) + "px; top: " + QString::number(note->y()) + "px; width: " + QString::number(note->groupWidth()) + "px\"";
395 
396     if (note->isGroup()) {
397         stream << '\n' << spaces.fill(' ', indent) << "<table" << freeStyle << ">\n"; // Note content is expected to be on the same HTML line, but NOT groups
398         int i = 0;
399         for (Note *child = note->firstChild(); child; child = child->next()) {
400             stream << spaces.fill(' ', indent);
401             if (i == 0)
402                 stream << " <tr><td class=\"groupHandle\"><img src=\"" << imagesFolderName << (note->isFolded() ? "expand_group_" : "fold_group_") << backgroundColorName << ".png"
403                 << "\" width=\"" << Note::EXPANDER_WIDTH << "\" height=\"" << Note::EXPANDER_HEIGHT << "\"></td>\n";
404             else if (i == 1)
405                 stream << " <tr><td class=\"freeSpace\" rowspan=\"" << note->countDirectChilds() << "\"></td>\n";
406             else
407                 stream << " <tr>\n";
408             stream << spaces.fill(' ', indent) << "  <td>";
409             exportNote(child, indent + 3);
410             stream << "</td>\n"
411             << spaces.fill(' ', indent) << " </tr>\n";
412             ++i;
413         }
414         stream << '\n' << spaces.fill(' ', indent) << "</table>\n" /*<< spaces.fill(' ', indent - 1)*/;
415     } else {
416         // Additional class for the content (link, netword, color...):
417         QString additionalClasses = note->content()->cssClass();
418         if (!additionalClasses.isEmpty())
419             additionalClasses = " " + additionalClasses;
420         // Assign the style of each associted tags:
421         for (State::List::Iterator it = note->states().begin(); it != note->states().end(); ++it)
422             additionalClasses += " tag_" + (*it)->id();
423         //stream << spaces.fill(' ', indent);
424         stream << "<table class=\"note" << additionalClasses << "\"" << freeStyle << "><tr>";
425         if (note->emblemsCount() > 0) {
426             stream << "<td class=\"tags\"><nobr>";
427             for (State::List::Iterator it = note->states().begin(); it != note->states().end(); ++it)
428                 if (!(*it)->emblem().isEmpty()) {
429                     int emblemSize = 16;
430                     QString iconFileName = copyIcon((*it)->emblem(), emblemSize);
431                     stream << "<img src=\"" << iconsFolderName << iconFileName
432                     << "\" width=\"" << emblemSize << "\" height=\"" << emblemSize
433                     << "\" alt=\"" << (*it)->textEquivalent() << "\" title=\"" << (*it)->fullName() << "\">";
434                 }
435             stream << "</nobr></td>";
436         }
437         stream << "<td>";
438         note->content()->exportToHTML(this, indent);
439         stream << "</td></tr></table>";
440     }
441 }
442 
writeBasketTree(BasketScene * currentBasket)443 void HTMLExporter::writeBasketTree(BasketScene *currentBasket)
444 {
445     stream << "  <ul class=\"tree\">\n";
446     writeBasketTree(currentBasket, exportedBasket, 3);
447     stream << "  </ul>\n";
448 }
449 
writeBasketTree(BasketScene * currentBasket,BasketScene * basket,int indent)450 void HTMLExporter::writeBasketTree(BasketScene *currentBasket, BasketScene *basket, int indent)
451 {
452     // Compute variable HTML code:
453     QString spaces;
454     QString cssClass = (basket == currentBasket ? " class=\"current\"" : "");
455     QString link = "#";
456     if (currentBasket != basket) {
457         if (currentBasket == exportedBasket) {
458             link = basketsFolderName + basket->folderName().left(basket->folderName().length() - 1) + ".html";
459         } else if (basket == exportedBasket) {
460             link = "../../" + fileName;
461         } else {
462             link = basket->folderName().left(basket->folderName().length() - 1) + ".html";
463         }
464     }
465     QString spanStyle = "";
466     if (basket->backgroundColorSetting().isValid() || basket->textColorSetting().isValid()) {
467         spanStyle = " style=\"background-color: " + basket->backgroundColor().name() + "; color: " + basket->textColor().name() + "\"";
468     }
469 
470     // Write the basket tree line:
471     stream <<
472     spaces.fill(' ', indent) << "<li><a" << cssClass << " href=\"" << link << "\">"
473     "<span" << spanStyle << " title=\"" << Tools::textToHTMLWithoutP(basket->basketName()) << "\">"
474     "<img src=\"" << iconsFolderName <<  copyIcon(basket->icon(), 16) << "\" width=\"16\" height=\"16\" alt=\"\">" << Tools::textToHTMLWithoutP(basket->basketName()) << "</span></a>";
475 
476     // Write the sub-baskets lines & end the current one:
477     BasketListViewItem *item = Global::bnpView->listViewItemForBasket(basket);
478     if (item->childCount() >= 0) {
479         stream <<
480         "\n" <<
481         spaces.fill(' ', indent) << " <ul>\n";
482         for (int i = 0; i < item->childCount(); i++)
483             writeBasketTree(currentBasket, ((BasketListViewItem*)item->child(i))->basket(), indent + 2);
484         stream <<
485         spaces.fill(' ', indent) << " </ul>\n" <<
486         spaces.fill(' ', indent) << "</li>\n";
487     } else {
488         stream << "</li>\n";
489     }
490 }
491 
492 /** Save an icon to a folder.
493   * If an icon with the same name already exist in the destination,
494   * it is assumed the icon is already copied, so no action is took.
495   * It is optimized so that you can have an empty folder receiving the icons
496   * and call copyIcon() each time you encounter one during export process.
497   */
copyIcon(const QString & iconName,int size)498 QString HTMLExporter::copyIcon(const QString &iconName, int size)
499 {
500     if (iconName.isEmpty())
501         return "";
502 
503     // Sometimes icon can be "favicons/www.kde.org", we replace the '/' with a '_'
504     QString fileName = iconName; // QString::replace() isn't const, so I must copy the string before
505     fileName = "ico" + QString::number(size) + "_" + fileName.replace("/", "_") + ".png";
506     QString fullPath = iconsFolderPath + fileName;
507     if (!QFile::exists(fullPath))
508         DesktopIcon(iconName, size).save(fullPath, "PNG");
509     return fileName;
510 }
511 
512 /** Done: Sometimes we can call two times copyFile() with the same srcPath and dataFolderPath
513   *       (eg. when exporting basket to HTML with two links to same filename
514   *            (but not necesary same path, as in "/home/foo.txt" and "/foo.txt") )
515   *       The first copy isn't yet started, so the dest file isn't created and this method
516   *       returns the same filename !!!!!!!!!!!!!!!!!!!!
517   */
copyFile(const QString & srcPath,bool createIt)518 QString HTMLExporter::copyFile(const QString &srcPath, bool createIt)
519 {
520     QString fileName = Tools::fileNameForNewFile(QUrl::fromLocalFile(srcPath).fileName(), dataFolderPath);
521     QString fullPath = dataFolderPath + fileName;
522 
523     if (!currentBasket->isEncrypted()){
524         if (createIt) {
525             // We create the file to be sure another very near call to copyFile() willn't choose the same name:
526             QFile file(QUrl::fromLocalFile(fullPath).path());
527             if (file.open(QIODevice::WriteOnly))
528                 file.close();
529             // And then we copy the file AND overwriting the file we juste created:
530             KIO::file_copy(QUrl::fromLocalFile(srcPath), QUrl::fromLocalFile(fullPath), 0666, KIO::HideProgressInfo | KIO::Resume | KIO::Overwrite);
531         } else {
532             /*KIO::CopyJob *copyJob = */KIO::copy(QUrl::fromLocalFile(srcPath), QUrl::fromLocalFile(fullPath), KIO::DefaultFlags); // Do it as before
533         }
534     } else {
535         QByteArray array;
536         bool success = currentBasket->loadFromFile(srcPath, &array);
537 
538         if (success){
539             saveToFile(fullPath, array);
540         } else {
541             qDebug() << "Unable to load encrypted file " << srcPath;
542         }
543     }
544 
545     return fileName;
546 }
547 
saveToFile(const QString & fullPath,const QByteArray & array)548 void HTMLExporter::saveToFile(const QString& fullPath, const QByteArray& array)
549 {
550     QFile file(QUrl::fromLocalFile(fullPath).path());
551     if (file.open(QIODevice::WriteOnly)){
552         file.write(array, array.size());
553         file.close();
554     } else {
555         qDebug() << "Unable to open file for writing: " << fullPath;
556     }
557 }
558