]> git.sven.stormbind.net Git - sven/vym.git/blob - src/export-html.cpp
Replace Pierre as the maintainer
[sven/vym.git] / src / export-html.cpp
1 #include "export-html.h"
2
3 #include <QMessageBox>
4
5 #include "branchobj.h"
6 #include "mainwindow.h"
7 #include "warningdialog.h"
8
9 extern QString flagsPath;
10 extern Main *mainWindow;
11 extern QString vymVersion;
12 extern QString vymHome;
13
14 extern FlagRowMaster *standardFlagsMaster;
15 extern FlagRowMaster *userFlagsMaster;
16
17 ExportHTML::ExportHTML() : ExportBase() { init(); }
18
19 ExportHTML::ExportHTML(VymModel *m) : ExportBase(m) { init(); }
20
21 void ExportHTML::init()
22 {
23     exportName = "HTML";
24     extension = ".html";
25     frameURLs = true;
26 }
27
28 QString ExportHTML::getBranchText(BranchItem *current)
29 {
30     if (current) {
31         bool vis = false;
32         QRectF hr;
33         LinkableMapObj *lmo = current->getLMO();
34         if (lmo) {
35             hr = ((BranchObj *)lmo)->getBBoxHeading();
36             vis = lmo->isVisibleObj();
37         }
38         QString col;
39         QString id = model->getSelectString(current);
40         if (dia.useTextColor)
41             col = QString("style='color:%1'")
42                       .arg(current->getHeadingColor().name());
43         QString s = QString("<span class='vym-branch-%1' %2 id='%3'>")
44                         .arg(current->depth())
45                         .arg(col)
46                         .arg(id);
47         QString url = current->getURL();
48         QString heading = quoteMeta(current->getHeadingPlain());
49
50         // Task flags
51         QString taskFlags;
52         if (dia.useTaskFlags) {
53             Task *task = current->getTask();
54             if (task) {
55                 QString taskName = task->getIconString();
56                 taskFlags +=
57                     QString("<img src=\"flags/flag-%1.png\" alt=\"%2\">")
58                         .arg(taskName)
59                         .arg(QObject::tr("Flag: %1", "Alt tag in HTML export")
60                                  .arg(taskName));
61             }
62         }
63
64         // Standard and user flags
65         QString flags;
66         if (dia.useUserFlags) {
67             Flag *f;
68             foreach (QUuid uid, current->activeFlagUids()) {
69                 activeFlags << uid;
70
71                 f = standardFlagsMaster->findFlagByUid(uid);
72                 if (!f)
73                     f = userFlagsMaster->findFlagByUid(uid);
74
75                 if (f)
76                     flags +=
77                         QString(
78                             "<img width=\"32px\" alt=\"%1\" src=\"flags/%2\">")
79                             .arg(QObject::tr("Flag: %1",
80                                              "Alt tag in HTML export")
81                                      .arg(f->getName()))
82                             .arg(uid.toString() +
83                                  f->getImageObj()->getExtension());
84             }
85         }
86
87         // Numbering
88         QString number;
89         if (dia.useNumbering)
90             number = getSectionString(current) + " ";
91
92         // URL
93         if (!url.isEmpty()) {
94             s += QString("<a href=\"%1\">%2<img src=\"flags/flag-url.png\" "
95                          "alt=\"%3\"></a>")
96                      .arg(url)
97                      .arg(number + taskFlags + heading + flags)
98                      .arg(QObject::tr("Flag: url", "Alt tag in HTML export"));
99
100             QRectF fbox = current->getBBoxURLFlag();
101             if (vis)
102                 imageMap += QString("  <area shape='rect' coords='%1,%2,%3,%4' "
103                                     "href='%5' alt='External link: %6'>\n")
104                                 .arg(fbox.left() - offset.x())
105                                 .arg(fbox.top() - offset.y())
106                                 .arg(fbox.right() - offset.x())
107                                 .arg(fbox.bottom() - offset.y())
108                                 .arg(url)
109                                 .arg(heading);
110         }
111         else
112             s += number + taskFlags + heading + flags;
113
114         s += "</span>";
115
116         // Create imagemap
117         if (vis && dia.includeMapImage)
118             imageMap += QString("  <area shape='rect' coords='%1,%2,%3,%4' "
119                                 "href='#%5' alt='%6'>\n")
120                             .arg(hr.left() - offset.x())
121                             .arg(hr.top() - offset.y())
122                             .arg(hr.right() - offset.x())
123                             .arg(hr.bottom() - offset.y())
124                             .arg(id)
125                             .arg(heading);
126
127         // Include images experimental
128         if (dia.includeImages) {
129             int imageCount = current->imageCount();
130             ImageItem *image;
131             QString imageName;
132             for (int i = 0; i < imageCount; i++) {
133                 image = current->getImageNum(i);
134                 imageName = image->getUniqueFilename();
135                 image->saveImage(dirPath + "/" + imageName);
136                 s += "</br><img src=\"" + imageName;
137                 s += "\" alt=\"" +
138                      QObject::tr("Image: %1", "Alt tag in HTML export")
139                          .arg(image->getOriginalFilename());
140                 s += "\"></br>";
141             }
142         }
143
144         // Include note
145         if (!current->isNoteEmpty()) {
146             VymNote note = current->getNote();
147             QString n;
148             if (note.isRichText()) {
149                 n = note.getText();
150                 QRegExp re("<p.*>");
151                 re.setMinimal(true);
152                 if (current->getNote().getFontHint() == "fixed")
153                     n.replace(re, "<p class=\"vym-fixed-note-paragraph\">");
154                 else
155                     n.replace(re, "<p class=\"vym-note-paragraph\">");
156
157                 re.setPattern("</?html>");
158                 n.replace(re, "");
159
160                 re.setPattern("</?head.*>");
161                 n.replace(re, "");
162
163                 re.setPattern("</?body.*>");
164                 n.replace(re, "");
165
166                 re.setPattern("</?meta.*>");
167                 n.replace(re, "");
168
169                 re.setPattern("<style.*>.*</style>");
170                 n.replace(re, "");
171
172                 // re.setPattern("<!DOCTYPE.*>");
173                 // n.replace(re,"");
174             }
175             else {
176                 n = current->getNoteASCII(0, 0)
177                         .replace("<", "&lt;")
178                         .replace(">", "&gt;");
179                 n.replace("\n", "<br/>");
180                 if (current->getNote().getFontHint() == "fixed")
181                     n = "<pre>" + n + "</pre>";
182             }
183             s += "\n<table class=\"vym-note\"><tr><td "
184                  "class=\"vym-note-flag\">\n<td>\n" +
185                  n + "\n</td></tr></table>\n";
186         }
187         return s;
188     }
189     return QString();
190 }
191
192 QString ExportHTML::buildList(BranchItem *current)
193 {
194     QString r;
195
196     uint i = 0;
197     uint visChilds = 0;
198
199     BranchItem *bi = current->getFirstBranch();
200
201     QString ind = "\n" + indent(current->depth() + 1, false);
202
203     QString sectionBegin;
204     QString sectionEnd;
205     QString itemBegin;
206     QString itemEnd;
207
208     switch (current->depth() + 1) {
209     case 0:
210         sectionBegin = "";
211         sectionEnd = "";
212         itemBegin = "<h1>";
213         itemEnd = "</h1>";
214         break;
215     case 1:
216         sectionBegin = "";
217         sectionEnd = "";
218         itemBegin = "<h2>";
219         itemEnd = "</h2>";
220         break;
221     default:
222         sectionBegin =
223             "<ul " +
224             QString("class=\"vym-list-ul-%1\"").arg(current->depth() + 1) + ">";
225         sectionEnd = "</ul>";
226         itemBegin = "  <li>";
227         itemEnd = "  </li>";
228         break;
229     }
230
231     if (bi && !bi->hasHiddenExportParent() && !bi->isHidden()) {
232         r += ind + sectionBegin;
233         while (bi) {
234             if (!bi->hasHiddenExportParent() && !bi->isHidden()) {
235                 visChilds++;
236                 r += ind + itemBegin;
237                 r += getBranchText(bi);
238
239                 if (itemBegin.startsWith("<h"))
240                     r += itemEnd + buildList(bi);
241                 else
242                     r += buildList(bi) + itemEnd;
243             }
244             i++;
245             bi = current->getBranchNum(i);
246         }
247         r += ind + sectionEnd;
248     }
249
250     return r;
251 }
252
253 QString ExportHTML::createTOC()
254 {
255     QString toc;
256     QString number;
257     toc += "<table class=\"vym-toc\">\n";
258     toc += "<tr><td class=\"vym-toc-title\">\n";
259     toc += QObject::tr("Contents:", "Used in HTML export");
260     toc += "\n";
261     toc += "</td></tr>\n";
262     toc += "<tr><td>\n";
263     BranchItem *cur = NULL;
264     BranchItem *prev = NULL;
265     model->nextBranch(cur, prev);
266     while (cur) {
267         if (!cur->hasHiddenExportParent() && !cur->hasScrolledParent()) {
268             if (dia.useNumbering)
269                 number = getSectionString(cur);
270             toc +=
271                 QString("<div class=\"vym-toc-branch-%1\">").arg(cur->depth());
272             toc += QString("<a href=\"#%1\"> %2 %3</a></br>\n")
273                        .arg(model->getSelectString(cur))
274                        .arg(number)
275                        .arg(quoteMeta(cur->getHeadingPlain()));
276             toc += "</div>";
277         }
278         model->nextBranch(cur, prev);
279     }
280     toc += "</td></tr>\n";
281     toc += "</table>\n";
282     return toc;
283 }
284
285 void ExportHTML::doExport(bool useDialog)
286 {
287     // Setup dialog and read settings
288     dia.setMapName(model->getMapName());
289     dia.setFilePath(model->getFilePath());
290     dia.readSettings();
291
292     if (dirPath != defaultDirPath)
293         dia.setDirectory(dirPath);
294
295     if (useDialog) {
296         if (dia.exec() != QDialog::Accepted)
297             return;
298         model->setChanged();
299     }
300
301     // Check, if warnings should be used before overwriting
302     // the output directory
303     if (dia.getDir().exists() && dia.getDir().count() > 0) {
304         WarningDialog warn;
305         warn.showCancelButton(true);
306         warn.setText(QString("The directory %1 is not empty.\n"
307                              "Do you risk to overwrite some of its contents?")
308                          .arg(dia.getDir().absolutePath()));
309         warn.setCaption("Warning: Directory not empty");
310         warn.setShowAgainName("mainwindow/export-XML-overwrite-dir");
311
312         if (warn.exec() != QDialog::Accepted) {
313             mainWindow->statusMessage(QString(QObject::tr("Export aborted.")));
314             return;
315         }
316     }
317
318     dirPath = dia.getDir().absolutePath();
319     filePath = getFilePath();
320
321     // Copy CSS file
322     if (dia.css_copy) {
323         cssSrc = dia.getCssSrc();
324         cssDst = dirPath + "/" + basename(cssSrc);
325         if (cssSrc.isEmpty()) {
326             QMessageBox::critical(
327                 0, QObject::tr("Critical"),
328                 QObject::tr("Could not find stylesheet %1").arg(cssSrc));
329             return;
330         }
331         QFile src(cssSrc);
332         QFile dst(cssDst);
333         if (dst.exists())
334             dst.remove();
335
336         if (!src.copy(cssDst)) {
337             QMessageBox::critical(
338                 0, QObject::tr("Error", "ExportHTML"),
339                 QObject::tr("Could not copy\n%1 to\n%2", "ExportHTML")
340                     .arg(cssSrc)
341                     .arg(cssDst));
342             return;
343         }
344     }
345
346     // Open file for writing
347     QFile file(filePath);
348     if (!file.open(QIODevice::WriteOnly)) {
349         QMessageBox::critical(
350             0, QObject::tr("Critical Export Error"),
351             QObject::tr("Trying to save HTML file:") + "\n\n" +
352                 QObject::tr("Could not write %1").arg(filePath));
353         mainWindow->statusMessage(QString(QObject::tr("Export failed.")));
354         return;
355     }
356     QTextStream ts(&file);
357     ts.setCodec("UTF-8");
358
359     // Hide stuff during export
360     model->setExportMode(true);
361
362     // Write header
363     ts << "<html>";
364     ts << "\n<meta http-equiv=\"content-type\" content=\"text/html; "
365           "charset=UTF-8\"> ";
366     ts << "\n<meta name=\"generator=\" content=\" vym - view your mind - " +
367               vymVersion + " - " + vymHome + "\">";
368     ts << "\n<meta name=\"author\" content=\"" + quoteMeta(model->getAuthor()) +
369               "\"> ";
370     ts << "\n<meta name=\"description\" content=\"" +
371               quoteMeta(model->getComment()) + "\"> ";
372     ts << "\n<link rel='stylesheet' id='css.stylesheet' href='"
373        << basename(cssDst) << "' />\n";
374     QString title = model->getTitle();
375     if (title.isEmpty())
376         title = model->getMapName();
377     ts << "\n<head><title>" + quoteMeta(title) + "</title></head>";
378     ts << "\n<body>\n";
379
380     // Include image
381     // (be careful: this resets Export mode, so call before exporting branches)
382     if (dia.includeMapImage) {
383         QString mapName = getMapName();
384         ts << "<center><img src=\"" << mapName << ".png\"";
385         ts << "alt=\""
386            << QString("Image of map: %1.vym").arg(mapName)
387            << "\"";
388         ts << " usemap='#imagemap'></center>\n";
389         offset =
390             model->exportImage(dirPath + "/" + mapName + ".png", false, "PNG");
391     }
392
393     // Include table of contents
394     if (dia.useTOC)
395         ts << createTOC();
396
397     // reset flags
398     model->resetUsedFlags();
399
400     // Main loop over all mapcenters
401     ts << buildList(model->getRootItem()) << "\n";
402
403     // Imagemap
404     ts << "<map name='imagemap'>\n" + imageMap + "</map>\n";
405
406     // Write footer
407     ts << "<hr/>\n";
408     ts << "<table class=\"vym-footer\">   \n\
409         <tr> \n\
410         <td class=\"vym-footerL\">" +
411               filePath + "</td> \n\
412             <td class=\"vym-footerC\">" +
413               model->getDate() + "</td> \n\
414             <td class=\"vym-footerR\"> <a href='" +
415               vymHome + "'>vym " + vymVersion + "</a></td> \n\
416             </tr> \n \
417             </table>\n";
418     ts << "</body></html>";
419     file.close();
420
421     QString flagsBasePath = dia.getDir().absolutePath() + "/flags";
422     QDir d(flagsBasePath);
423     if (!d.exists()) {
424         if (!dia.getDir().mkdir("flags")) {
425             QMessageBox::critical(
426                 0, QObject::tr("Critical"),
427                 QObject::tr("Trying to create directory for flags:") + "\n\n" +
428                     QObject::tr("Could not create %1").arg(flagsBasePath));
429             return;
430         }
431     }
432     Flag *f;
433     foreach (QUuid uid, activeFlags) {
434         f = standardFlagsMaster->findFlagByUid(uid);
435         if (!f)
436             f = userFlagsMaster->findFlagByUid(uid);
437
438         if (f) {
439             ImageObj *io = f->getImageObj();
440             if (io)
441                 io->save(flagsBasePath + "/" + uid.toString() +
442                          io->getExtension());
443         }
444     }
445
446     if (!dia.postscript.isEmpty()) {
447         VymProcess p;
448         p.runScript(dia.postscript, dirPath + "/" + filePath);
449     }
450
451     displayedDestination = filePath;
452
453     result = ExportBase::Success;
454
455     QStringList args;
456     args << filePath;
457     args << dirPath;
458     completeExport(args);
459
460     dia.saveSettings();
461     model->setExportMode(false);
462 }