File indexing completed on 2024-04-14 15:50:56

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 "htmlexporter.h"
0008 
0009 #include "basketlistview.h"
0010 #include "basketscene.h"
0011 #include "bnpview.h"
0012 #include "common.h"
0013 #include "config.h"
0014 #include "linklabel.h"
0015 #include "note.h"
0016 #include "notecontent.h"
0017 #include "tools.h"
0018 
0019 #include <KAboutData>
0020 #include <KConfig>
0021 #include <KConfigGroup>
0022 #include <KIconLoader>
0023 #include <KLocalizedString>
0024 #include <KMessageBox>
0025 
0026 #include <KIO/CopyJob> //For KIO::copy
0027 #include <KIO/Job>     //For KIO::file_copy
0028 
0029 #include <QApplication>
0030 #include <QFileDialog>
0031 #include <QLocale>
0032 #include <QProgressDialog>
0033 #include <QtCore/QDir>
0034 #include <QtCore/QFile>
0035 #include <QtCore/QList>
0036 #include <QtCore/QTextStream>
0037 #include <QtGui/QPainter>
0038 #include <QtGui/QPixmap>
0039 
0040 HTMLExporter::HTMLExporter(BasketScene *basket)
0041     : dialog(new QProgressDialog())
0042 {
0043     QDir dir;
0044 
0045     // Compute a default file name & path:
0046     KConfigGroup config = Global::config()->group("Export to HTML");
0047     QString folder = config.readEntry("lastFolder", QDir::homePath()) + '/';
0048     QString url = folder + QString(basket->basketName()).replace('/', '_') + ".html";
0049 
0050     // Ask a file name & path to the user:
0051     QString filter = "*.html *.htm|" + i18n("HTML Documents") + "\n*|" + i18n("All Files");
0052     QString destination = url;
0053     for (bool askAgain = true; askAgain;) {
0054         // Ask:
0055         destination = QFileDialog::getSaveFileName(nullptr, i18n("Export to HTML"), destination, filter);
0056         // User canceled?
0057         if (destination.isEmpty())
0058             return;
0059         // File already existing? Ask for overriding:
0060         if (dir.exists(destination)) {
0061             int result = KMessageBox::questionYesNoCancel(
0062                 nullptr, "<qt>" + i18n("The file <b>%1</b> already exists. Do you really want to overwrite it?", QUrl::fromLocalFile(destination).fileName()), i18n("Overwrite File?"), KGuiItem(i18n("&Overwrite"), "document-save"));
0063             if (result == KMessageBox::Cancel)
0064                 return;
0065             else if (result == KMessageBox::Yes)
0066                 askAgain = false;
0067         } else
0068             askAgain = false;
0069     }
0070 
0071     // Create the progress dialog that will always be shown during the export:
0072     dialog->setWindowTitle(i18n("Export to HTML"));
0073     dialog->setLabelText(i18n("Exporting to HTML. Please wait..."));
0074     dialog->setCancelButton(nullptr);
0075     dialog->setAutoClose(true);
0076     dialog->show();
0077 
0078     // Remember the last folder used for HTML exploration:
0079     config.writeEntry("lastFolder", QUrl::fromLocalFile(destination).adjusted(QUrl::RemoveFilename).path());
0080     config.sync();
0081 
0082     prepareExport(basket, destination);
0083     exportBasket(basket, /*isSubBasketScene*/ false);
0084 
0085     dialog->setValue(dialog->value() + 1); // Finishing finished
0086 }
0087 
0088 HTMLExporter::~HTMLExporter()
0089 {
0090 }
0091 
0092 void HTMLExporter::prepareExport(BasketScene *basket, const QString &fullPath)
0093 {
0094     dialog->setRange(0, /*Preparation:*/ 1 + /*Finishing:*/ 1 + /*Basket:*/ 1 + /*SubBaskets:*/ Global::bnpView->basketCount(Global::bnpView->listViewItemForBasket(basket)));
0095     dialog->setValue(0);
0096     qApp->processEvents();
0097 
0098     // Remember the file path chosen by the user:
0099     filePath = fullPath;
0100     fileName = QUrl::fromLocalFile(fullPath).fileName();
0101     exportedBasket = basket;
0102     currentBasket = nullptr;
0103 
0104     BasketListViewItem *item = Global::bnpView->listViewItemForBasket(basket);
0105     withBasketTree = (item->childCount() >= 0);
0106 
0107     // Create and empty the files folder:
0108     QString filesFolderPath = i18nc("HTML export folder (files)", "%1_files", filePath) + '/'; // eg.: "/home/seb/foo.html_files/"
0109     Tools::deleteRecursively(filesFolderPath);
0110     QDir dir;
0111     dir.mkdir(filesFolderPath);
0112 
0113     // Create sub-folders:
0114     iconsFolderPath = filesFolderPath + i18nc("HTML export folder (icons)", "icons") + '/';       // eg.: "/home/seb/foo.html_files/icons/"
0115     imagesFolderPath = filesFolderPath + i18nc("HTML export folder (images)", "images") + '/';    // eg.: "/home/seb/foo.html_files/images/"
0116     basketsFolderPath = filesFolderPath + i18nc("HTML export folder (baskets)", "baskets") + '/'; // eg.: "/home/seb/foo.html_files/baskets/"
0117     dir.mkdir(iconsFolderPath);
0118     dir.mkdir(imagesFolderPath);
0119     dir.mkdir(basketsFolderPath);
0120 
0121     dialog->setValue(dialog->value() + 1); // Preparation finished
0122 }
0123 
0124 void HTMLExporter::exportBasket(BasketScene *basket, bool isSubBasket)
0125 {
0126     if (!basket->isLoaded()) {
0127         basket->load();
0128     }
0129 
0130     currentBasket = basket;
0131 
0132     bool hasBackgroundColor = false;
0133     bool hasTextColor = false;
0134 
0135     if (basket->backgroundColorSetting().isValid()) {
0136         hasBackgroundColor = true;
0137         backgroundColorName = basket->backgroundColor().name().toLower().mid(1);
0138     }
0139     if (basket->textColorSetting().isValid()) {
0140         hasTextColor = true;
0141     }
0142 
0143     // Compute the absolute & relative paths for this basket:
0144     filesFolderPath = i18nc("HTML export folder (files)", "%1_files", filePath) + '/';
0145     if (isSubBasket) {
0146         basketFilePath = basketsFolderPath + basket->folderName().left(basket->folderName().length() - 1) + ".html";
0147         filesFolderName = QStringLiteral("../");
0148         dataFolderName = basket->folderName().left(basket->folderName().length() - 1) + '-' + i18nc("HTML export folder (data)", "data") + '/';
0149         dataFolderPath = basketsFolderPath + dataFolderName;
0150         basketsFolderName = QString();
0151     } else {
0152         basketFilePath = filePath;
0153         filesFolderName = i18nc("HTML export folder (files)", "%1_files", QUrl::fromLocalFile(filePath).fileName()) + '/';
0154         dataFolderName = filesFolderName + i18nc("HTML export folder (data)", "data") + '/';
0155         dataFolderPath = filesFolderPath + i18nc("HTML export folder (data)", "data") + '/';
0156         basketsFolderName = filesFolderName + i18nc("HTML export folder (baskets)", "baskets") + '/';
0157     }
0158     iconsFolderName = (isSubBasket ? QStringLiteral("../") : filesFolderName) + i18nc("HTML export folder (icons)", "icons") + QLatin1Char('/');    // eg.: "foo.html_files/icons/"   or "../icons/"
0159     imagesFolderName = (isSubBasket ? QStringLiteral("../") : filesFolderName) + i18nc("HTML export folder (images)", "images") + QLatin1Char('/'); // eg.: "foo.html_files/images/"  or "../images/"
0160 
0161     qDebug() << "Exporting ================================================";
0162     qDebug() << "  filePath:" << filePath;
0163     qDebug() << "  basketFilePath:" << basketFilePath;
0164     qDebug() << "  filesFolderPath:" << filesFolderPath;
0165     qDebug() << "  filesFolderName:" << filesFolderName;
0166     qDebug() << "  iconsFolderPath:" << iconsFolderPath;
0167     qDebug() << "  iconsFolderName:" << iconsFolderName;
0168     qDebug() << "  imagesFolderPath:" << imagesFolderPath;
0169     qDebug() << "  imagesFolderName:" << imagesFolderName;
0170     qDebug() << "  dataFolderPath:" << dataFolderPath;
0171     qDebug() << "  dataFolderName:" << dataFolderName;
0172     qDebug() << "  basketsFolderPath:" << basketsFolderPath;
0173     qDebug() << "  basketsFolderName:" << basketsFolderName;
0174 
0175     // Create the data folder for this basket:
0176     QDir dir;
0177     dir.mkdir(dataFolderPath);
0178 
0179     // Generate basket icons:
0180     QString basketIcon16 = iconsFolderName + copyIcon(basket->icon(), 16);
0181     QString basketIcon32 = iconsFolderName + copyIcon(basket->icon(), 32);
0182 
0183     // Generate the [+] image for groups:
0184     QPixmap expandGroup(Note::EXPANDER_WIDTH, Note::EXPANDER_HEIGHT);
0185     if (hasBackgroundColor) {
0186         expandGroup.fill(basket->backgroundColor());
0187     } else {
0188          expandGroup.fill(QColor(Qt::GlobalColor::transparent));
0189     }
0190     QPainter painter(&expandGroup);
0191     if (hasBackgroundColor) {
0192         Note::drawExpander(&painter, 0, 0, basket->backgroundColor(), /*expand=*/true, basket);
0193     } else {
0194         Note::drawExpander(&painter, 0, 0,QColor(Qt::GlobalColor::transparent), /*expand=*/true, basket);
0195     }
0196     painter.end();
0197     if (hasBackgroundColor) {
0198         expandGroup.save(imagesFolderPath + "expand_group_" + backgroundColorName + ".png", "PNG");
0199     } else {
0200         expandGroup.save(imagesFolderPath + "expand_group_transparent.png", "PNG");
0201     }
0202 
0203     // Generate the [-] image for groups:
0204     QPixmap foldGroup(Note::EXPANDER_WIDTH, Note::EXPANDER_HEIGHT);
0205     if (hasBackgroundColor) {
0206         foldGroup.fill(basket->backgroundColor());
0207     } else {
0208         foldGroup.fill(QColor(Qt::GlobalColor::transparent));
0209     }
0210     painter.begin(&foldGroup);
0211     if (hasBackgroundColor) {
0212         Note::drawExpander(&painter, 0, 0, basket->backgroundColor(), /*expand=*/false, basket);
0213     } else {
0214         Note::drawExpander(&painter, 0, 0, QColor(Qt::GlobalColor::transparent), /*expand=*/false, basket);
0215     }
0216 
0217     painter.end();
0218     if (hasBackgroundColor) {
0219         foldGroup.save(imagesFolderPath + "fold_group_" + backgroundColorName + ".png", "PNG");
0220     } else {
0221         foldGroup.save(imagesFolderPath + "fold_group_transparent.png", "PNG");
0222     }
0223 
0224     // Open the file to write:
0225     QFile file(basketFilePath);
0226     if (!file.open(QIODevice::WriteOnly))
0227         return;
0228     stream.setDevice(&file);
0229     stream.setCodec("UTF-8");
0230 
0231     // Output the header:
0232     QString borderColor;
0233     if (hasBackgroundColor && hasTextColor) {
0234         borderColor = Tools::mixColor(basket->backgroundColor(), basket->textColor()).name();
0235     } else if (hasBackgroundColor) {
0236         borderColor = Tools::mixColor(basket->backgroundColor(), QColor(Qt::GlobalColor::black)).name();
0237     } else if (hasTextColor) {
0238         borderColor = Tools::mixColor(QColor(Qt::GlobalColor::white), basket->textColor()).name();
0239     }
0240     stream << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n"
0241               "<html>\n"
0242               " <head>\n"
0243               "  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"
0244               "  <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"><meta name=\"Generator\" content=\""
0245            << QGuiApplication::applicationDisplayName() << " " << VERSION << " " << KAboutData::applicationData().homepage()
0246            << "\">\n"
0247               "  <style type=\"text/css\">\n"
0248               //      "   @media print {\n"
0249               //      "    span.printable { display: inline; }\n"
0250               //      "   }\n"
0251               "   body { margin: 10px; font: 11px sans-serif; }\n" // TODO: Use user font
0252               "   h1 { text-align: center; }\n"
0253               "   img { border: none; vertical-align: middle; }\n";
0254     if (withBasketTree) {
0255         stream << "   .tree { margin: 0; padding: 1px 0 1px 1px; width: 150px; _width: 149px; overflow: hidden; float: left; }\n"
0256                   "   .tree ul { margin: 0 0 0 10px; padding: 0; }\n"
0257                   "   .tree li { padding: 0; margin: 0; list-style: none; }\n"
0258                   "   .tree a { display: block; padding: 1px; height: 16px; text-decoration: none;\n"
0259                   "             white-space: nowrap; word-wrap: normal; text-wrap: suppress; color: black; }\n"
0260                   "   .tree span { -moz-border-radius: 6px; display: block; float: left;\n"
0261                   "                line-height: 16px; height: 16px; vertical-align: middle; padding: 0 1px; }\n"
0262                   "   .tree img { vertical-align: top; padding-right: 1px; }\n"
0263                   "   .tree .current { background-color: "
0264                << qApp->palette().color(QPalette::Highlight).name()
0265                << "; "
0266                   "-moz-border-radius: 3px 0 0 3px; border-radius: 3px 0 0 3px; color: "
0267                << qApp->palette().color(QPalette::Highlight).name()
0268                << "; }\n"
0269                   "   .basketSurrounder { margin-left: 152px; _margin: 0; _float: right; }\n";
0270     }
0271 
0272     stream << "   .basket { ";
0273     if (hasBackgroundColor) {
0274         stream << "background-color: " << basket->backgroundColor().name() << "; ";
0275     }
0276     stream << "border: solid " << borderColor
0277            << " 1px; "
0278               "font: "
0279            << Tools::cssFontDefinition(basket->QGraphicsScene::font()) << "; ";
0280     if (hasTextColor) {
0281         stream << "color: " << basket->textColor().name() << "; ";
0282     }
0283     stream << "padding: 1px; width: 100%; }\n"
0284               "   table.basket { border-collapse: collapse; }\n"
0285               "   .basket * { padding: 0; margin: 0; }\n"
0286               "   .basket table { width: 100%; border-spacing: 0; _border-collapse: collapse; }\n"
0287               "   .column { vertical-align: top; }\n"
0288               "   .columnHandle { width: "
0289            << Note::RESIZER_WIDTH << "px; background: transparent url('" << imagesFolderName << "column_handle_";
0290     if (hasBackgroundColor) {
0291         stream << backgroundColorName;
0292     } else {
0293         stream << "transparent";
0294     }
0295     stream << ".png') repeat-y; }\n"
0296               "   .group { margin: 0; padding: 0; border-collapse: collapse; width: 100% }\n"
0297               "   .groupHandle { margin: 0; width: "
0298            << Note::GROUP_WIDTH
0299            << "px; text-align: center; }\n"
0300               "   .note { padding: 1px 2px; ";
0301     if (hasBackgroundColor) {
0302         stream << "background-color : " << basket->backgroundColor().name() << "; ";
0303     }
0304     stream << "width: 100%; }\n"
0305               "   .tags { width: 1px; white-space: nowrap; }\n"
0306               "   .tags img { padding-right: 2px; }\n";
0307     if (hasTextColor) {
0308         stream << LinkLook::soundLook->toCSS("sound", basket->textColor()) << LinkLook::fileLook->toCSS("file", basket->textColor())
0309                << LinkLook::localLinkLook->toCSS("local", basket->textColor()) << LinkLook::networkLinkLook->toCSS("network", basket->textColor())
0310                << LinkLook::launcherLook->toCSS("launcher", basket->textColor()) << LinkLook::crossReferenceLook->toCSS("cross_reference", basket->textColor());
0311     } else {
0312         stream << LinkLook::soundLook->toCSS("sound", QColor(Qt::GlobalColor::black)) << LinkLook::fileLook->toCSS("file", QColor(Qt::GlobalColor::black))
0313                << LinkLook::localLinkLook->toCSS("local", QColor(Qt::GlobalColor::black)) << LinkLook::networkLinkLook->toCSS("network", QColor(Qt::GlobalColor::black))
0314                << LinkLook::launcherLook->toCSS("launcher", QColor(Qt::GlobalColor::black)) << LinkLook::crossReferenceLook->toCSS("cross_reference", QColor(Qt::GlobalColor::black));
0315     }
0316     stream << "   .unknown { margin: 1px 2px; border: 1px solid " << borderColor << "; -moz-border-radius: 4px; }\n";
0317     QList<State *> states = basket->usedStates();
0318     QString statesCss;
0319     for (State::List::Iterator it = states.begin(); it != states.end(); ++it)
0320         statesCss += (*it)->toCSS(imagesFolderPath, imagesFolderName, basket->QGraphicsScene::font());
0321     stream << statesCss << "   .credits { text-align: right; margin: 3px 0 0 0; _margin-top: -17px; font-size: 80%; color: " << borderColor
0322            << "; }\n"
0323               "  </style>\n"
0324               "  <title>"
0325            << Tools::textToHTMLWithoutP(basket->basketName())
0326            << "</title>\n"
0327               "  <link rel=\"shortcut icon\" type=\"image/png\" href=\""
0328            << basketIcon16 << "\">\n";
0329     // Create the column handle image:
0330     QPixmap columnHandle(Note::RESIZER_WIDTH, 50);
0331     painter.begin(&columnHandle);
0332     if (hasBackgroundColor) {
0333         Note::drawInactiveResizer(&painter, 0, 0, columnHandle.height(), basket->backgroundColor(), /*column=*/true);
0334     } else {
0335         Note::drawInactiveResizer(&painter, 0, 0, columnHandle.height(), QColor(Qt::GlobalColor::black), /*column=*/true);
0336     }
0337     painter.end();
0338 
0339     if(hasBackgroundColor) {
0340         columnHandle.save(imagesFolderPath + "column_handle_" + backgroundColorName + ".png", "PNG");
0341     } else {
0342         columnHandle.save(imagesFolderPath + "column_handle_transparent.png", "PNG");
0343     }
0344 
0345     stream << " </head>\n"
0346               " <body>\n"
0347               "  <h1><img src=\""
0348            << basketIcon32 << "\" width=\"32\" height=\"32\" alt=\"\"> " << Tools::textToHTMLWithoutP(basket->basketName()) << "</h1>\n";
0349 
0350     if (withBasketTree)
0351         writeBasketTree(basket);
0352 
0353     // If filtering, only export filtered notes, inform to the user:
0354     // TODO: Filtering tags too!!
0355     // TODO: Make sure only filtered notes are exported!
0356     //  if (decoration()->filterData().isFiltering)
0357     //      stream <<
0358     //          "  <p>" << i18n("Notes matching the filter &quot;%1&quot;:", Tools::textToHTMLWithoutP(decoration()->filterData().string)) << "</p>\n";
0359 
0360     stream << "  <div class=\"basketSurrounder\">\n";
0361 
0362 
0363     stream << "   <div class=\"basket\" style=\"position: relative; min-width: 100%; min-height: calc(100vh - 100px); ";
0364     if (!basket->isColumnsLayout()) {
0365         stream << "height: " << basket->sceneRect().height() << "px; width: " << basket->sceneRect().width() << "px; ";
0366     }
0367     stream << "\">\n";
0368 
0369     if (basket->isColumnsLayout()) {
0370         stream << "   <table>\n"
0371                   "    <tr>\n";
0372     }
0373 
0374 
0375     for (Note *note = basket->firstNote(); note; note = note->next()) {
0376         exportNote(note, /*indent=*/(basket->isFreeLayout() ? 4 : 5));
0377     }
0378 
0379     // Output the footer:
0380     if (basket->isColumnsLayout()) {
0381         stream << "    </tr>\n"
0382                   "   </table>\n";
0383     }
0384 
0385     stream << "   </div>\n";
0386 
0387     stream << QString(
0388                   "  </div>\n"
0389                   "  <p class=\"credits\">%1</p>\n")
0390                   .arg(i18n("Made with <a href=\"%1\">%2</a> %3, a tool to take notes and keep information at hand.", KAboutData::applicationData().homepage(), QGuiApplication::applicationDisplayName(), VERSION));
0391 
0392     stream << " </body>\n"
0393               "</html>\n";
0394 
0395     file.close();
0396     stream.setDevice(nullptr);
0397     dialog->setValue(dialog->value() + 1); // Basket exportation finished
0398 
0399     // Recursively export child baskets:
0400     BasketListViewItem *item = Global::bnpView->listViewItemForBasket(basket);
0401     if (item->childCount() >= 0) {
0402         for (int i = 0; i < item->childCount(); i++) {
0403             exportBasket(((BasketListViewItem *)item->child(i))->basket(), /*isSubBasket=*/true);
0404         }
0405     }
0406 }
0407 
0408 void HTMLExporter::exportNote(Note *note, int indent)
0409 {
0410     QString spaces;
0411 
0412     if (note->isColumn()) {
0413         QString width;
0414         if (false /*TODO: DEBUG AND REENABLE: hasResizer()*/) {
0415             // As we cannot be precise in CSS (say eg. "width: 50%-40px;"),
0416             // we output a percentage that is approximately correct.
0417             // For instance, we compute the currently used percentage of width in the basket
0418             // and try make it the same on a 1024*768 display in a Web browser:
0419             int availableSpaceForColumnsInThisBasket = note->basket()->sceneRect().width() - (note->basket()->columnsCount() - 1) * Note::RESIZER_WIDTH;
0420             int availableSpaceForColumnsInBrowser = 1024 /* typical screen width */
0421                 - 25                                     /* window border and scrollbar width */
0422                 - 2 * 5                                  /* page margin */
0423                 - (note->basket()->columnsCount() - 1) * Note::RESIZER_WIDTH;
0424             if (availableSpaceForColumnsInThisBasket <= 0)
0425                 availableSpaceForColumnsInThisBasket = 1;
0426             int widthValue = (int)(availableSpaceForColumnsInBrowser * (double)note->groupWidth() / availableSpaceForColumnsInThisBasket);
0427             if (widthValue <= 0)
0428                 widthValue = 1;
0429             if (widthValue > 100)
0430                 widthValue = 100;
0431             width = QString(" width=\"%1%\"").arg(QString::number(widthValue));
0432         }
0433         stream << spaces.fill(' ', indent) << "<td class=\"column\"" << width << ">\n";
0434 
0435         // Export child notes:
0436         for (Note *child = note->firstChild(); child; child = child->next()) {
0437             stream << spaces.fill(' ', indent + 1);
0438             exportNote(child, indent + 1);
0439             stream << '\n';
0440         }
0441 
0442         stream << spaces.fill(' ', indent) << "</td>\n";
0443         if (note->hasResizer())
0444             stream << spaces.fill(' ', indent) << "<td class=\"columnHandle\"></td>\n";
0445         return;
0446     }
0447 
0448     QString freeStyle;
0449     if (note->isFree())
0450         freeStyle = " style=\"position: absolute; left: " + QString::number(note->x()) + "px; top: " + QString::number(note->y()) + "px; width: " + QString::number(note->groupWidth()) + "px\"";
0451 
0452     if (note->isGroup()) {
0453         stream << '\n' << spaces.fill(' ', indent) << "<table" << freeStyle << ">\n"; // Note content is expected to be on the same HTML line, but NOT groups
0454         int i = 0;
0455         for (Note *child = note->firstChild(); child; child = child->next()) {
0456             stream << spaces.fill(' ', indent);
0457             if (i == 0)
0458                 stream << " <tr><td class=\"groupHandle\"><img src=\"" << imagesFolderName << (note->isFolded() ? "expand_group_" : "fold_group_") << backgroundColorName << ".png"
0459                        << "\" width=\"" << Note::EXPANDER_WIDTH << "\" height=\"" << Note::EXPANDER_HEIGHT << "\"></td>\n";
0460             else if (i == 1)
0461                 stream << " <tr><td class=\"freeSpace\" rowspan=\"" << note->countDirectChilds() << "\"></td>\n";
0462             else
0463                 stream << " <tr>\n";
0464             stream << spaces.fill(' ', indent) << "  <td>";
0465             exportNote(child, indent + 3);
0466             stream << "</td>\n" << spaces.fill(' ', indent) << " </tr>\n";
0467             ++i;
0468         }
0469         stream << '\n' << spaces.fill(' ', indent) << "</table>\n" /*<< spaces.fill(' ', indent - 1)*/;
0470     } else {
0471         // Additional class for the content (link, netword, color...):
0472         QString additionalClasses = note->content()->cssClass();
0473         if (!additionalClasses.isEmpty())
0474             additionalClasses = ' ' + additionalClasses;
0475         // Assign the style of each associated tags:
0476         for (State::List::Iterator it = note->states().begin(); it != note->states().end(); ++it)
0477             additionalClasses += " tag_" + (*it)->id();
0478         // stream << spaces.fill(' ', indent);
0479         stream << "<table class=\"note" << additionalClasses << "\"" << freeStyle << "><tr>";
0480         if (note->emblemsCount() > 0) {
0481             stream << "<td class=\"tags\"><nobr>";
0482             for (State::List::Iterator it = note->states().begin(); it != note->states().end(); ++it)
0483                 if (!(*it)->emblem().isEmpty()) {
0484                     int emblemSize = 16;
0485                     QString iconFileName = copyIcon((*it)->emblem(), emblemSize);
0486                     stream << "<img src=\"" << iconsFolderName << iconFileName << "\" width=\"" << emblemSize << "\" height=\"" << emblemSize << "\" alt=\"" << (*it)->textEquivalent() << "\" title=\"" << (*it)->fullName() << "\">";
0487                 }
0488             stream << "</nobr></td>";
0489         }
0490         stream << "<td>";
0491         note->content()->exportToHTML(this, indent);
0492         stream << "</td></tr></table>";
0493     }
0494 }
0495 
0496 void HTMLExporter::writeBasketTree(BasketScene *currentBasket)
0497 {
0498     stream << "  <ul class=\"tree\">\n";
0499     writeBasketTree(currentBasket, exportedBasket, 3);
0500     stream << "  </ul>\n";
0501 }
0502 
0503 void HTMLExporter::writeBasketTree(BasketScene *currentBasket, BasketScene *basket, int indent)
0504 {
0505     // Compute variable HTML code:
0506     QString spaces;
0507     QString cssClass = (basket == currentBasket ? QStringLiteral(" class=\"current\"") : QString());
0508     QString link('#');
0509     if (currentBasket != basket) {
0510         if (currentBasket == exportedBasket) {
0511             link = basketsFolderName + basket->folderName().left(basket->folderName().length() - 1) + ".html";
0512         } else if (basket == exportedBasket) {
0513             link = "../../" + fileName;
0514         } else {
0515             link = basket->folderName().left(basket->folderName().length() - 1) + ".html";
0516         }
0517     }
0518     QString spanStyle = QStringLiteral(" style=\"");
0519     if (basket->backgroundColorSetting().isValid()) {
0520         spanStyle += "background-color: " + basket->backgroundColor().name() + "; ";
0521     }
0522     if (basket->textColorSetting().isValid()) {
0523         spanStyle += "color: " + basket->textColor().name() + "; ";
0524     }
0525     spanStyle += QStringLiteral("\"");
0526 
0527 
0528     // Write the basket tree line:
0529     stream << spaces.fill(' ', indent) << "<li><a" << cssClass << " href=\"" << link
0530            << "\">"
0531               "<span"
0532            << spanStyle << " title=\"" << Tools::textToHTMLWithoutP(basket->basketName())
0533            << "\">"
0534               "<img src=\""
0535            << iconsFolderName << copyIcon(basket->icon(), 16) << "\" width=\"16\" height=\"16\" alt=\"\">" << Tools::textToHTMLWithoutP(basket->basketName()) << "</span></a>";
0536 
0537     // Write the sub-baskets lines & end the current one:
0538     BasketListViewItem *item = Global::bnpView->listViewItemForBasket(basket);
0539     if (item->childCount() >= 0) {
0540         stream << "\n" << spaces.fill(' ', indent) << " <ul>\n";
0541         for (int i = 0; i < item->childCount(); i++)
0542             writeBasketTree(currentBasket, ((BasketListViewItem *)item->child(i))->basket(), indent + 2);
0543         stream << spaces.fill(' ', indent) << " </ul>\n" << spaces.fill(' ', indent) << "</li>\n";
0544     } else {
0545         stream << "</li>\n";
0546     }
0547 }
0548 
0549 /** Save an icon to a folder.
0550  * If an icon with the same name already exist in the destination,
0551  * it is assumed the icon is already copied, so no action is took.
0552  * It is optimized so that you can have an empty folder receiving the icons
0553  * and call copyIcon() each time you encounter one during export process.
0554  */
0555 QString HTMLExporter::copyIcon(const QString &iconName, int size)
0556 {
0557     if (iconName.isEmpty()) {
0558         return QString();
0559     }
0560 
0561     // Sometimes icon can be "favicons/www.kde.org", we replace the '/' with a '_'
0562     QString fileName = iconName; // QString::replace() isn't const, so I must copy the string before
0563     fileName = "ico" + QString::number(size) + '_' + fileName.replace('/', '_') + ".png";
0564     QString fullPath = iconsFolderPath + fileName;
0565     if (!QFile::exists(fullPath)) {
0566     KIconLoader::global()->loadIcon(iconName,KIconLoader::Desktop,size).save(fullPath, "PNG");
0567     }
0568     return fileName;
0569 }
0570 
0571 /** Done: Sometimes we can call two times copyFile() with the same srcPath and dataFolderPath
0572  *       (eg. when exporting basket to HTML with two links to same filename
0573  *            (but not necesary same path, as in "/home/foo.txt" and "/foo.txt") )
0574  *       The first copy isn't yet started, so the dest file isn't created and this method
0575  *       returns the same filename !!!!!!!!!!!!!!!!!!!!
0576  */
0577 QString HTMLExporter::copyFile(const QString &srcPath, bool createIt)
0578 {
0579     QString fileName = Tools::fileNameForNewFile(QUrl::fromLocalFile(srcPath).fileName(), dataFolderPath);
0580     QString fullPath = dataFolderPath + fileName;
0581 
0582     if (!currentBasket->isEncrypted()) {
0583         if (createIt) {
0584             // We create the file to be sure another very near call to copyFile() willn't choose the same name:
0585             QFile file(QUrl::fromLocalFile(fullPath).path());
0586             if (file.open(QIODevice::WriteOnly))
0587                 file.close();
0588             // And then we copy the file AND overwriting the file we just created:
0589             KIO::file_copy(QUrl::fromLocalFile(srcPath), QUrl::fromLocalFile(fullPath), 0666, KIO::HideProgressInfo | KIO::Resume | KIO::Overwrite);
0590         } else {
0591             /*KIO::CopyJob *copyJob = */ KIO::copy(QUrl::fromLocalFile(srcPath), QUrl::fromLocalFile(fullPath), KIO::DefaultFlags); // Do it as before
0592         }
0593     } else {
0594         QByteArray array;
0595         bool success = FileStorage::loadFromFile(srcPath, &array);
0596 
0597         if (success) {
0598             saveToFile(fullPath, array);
0599         } else {
0600             qDebug() << "Unable to load encrypted file " << srcPath;
0601         }
0602     }
0603 
0604     return fileName;
0605 }
0606 
0607 void HTMLExporter::saveToFile(const QString &fullPath, const QByteArray &array)
0608 {
0609     QFile file(QUrl::fromLocalFile(fullPath).path());
0610     if (file.open(QIODevice::WriteOnly)) {
0611         file.write(array, array.size());
0612         file.close();
0613     } else {
0614         qDebug() << "Unable to open file for writing: " << fullPath;
0615     }
0616 }