File indexing completed on 2023-10-03 05:13:34
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 "%1":", 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 }