]> git.sven.stormbind.net Git - sven/vym.git/blob - src/xml-vym.cpp
New upstream version 2.9.22
[sven/vym.git] / src / xml-vym.cpp
1 #include "xml-vym.h"
2
3 #include <QColor>
4 #include <QMessageBox>
5 #include <QTextStream>
6 #include <typeinfo>
7
8 #include "attributeitem.h"
9 #include "branchitem.h"
10 #include "flag.h"
11 #include "linkablemapobj.h"
12 #include "mainwindow.h"
13 #include "misc.h"
14 #include "settings.h"
15 #include "slideitem.h"
16 #include "task.h"
17 #include "taskmodel.h"
18 #include "xlinkitem.h"
19
20 extern Main *mainWindow;
21 extern Settings settings;
22 extern TaskModel *taskModel;
23 extern QString vymVersion;
24
25 parseVYMHandler::parseVYMHandler()
26 {
27     // Default is to load everything
28     contentFilter = 0x0000; // TODO  use filters for all content types below
29 }
30
31 void parseVYMHandler::setContentFilter(const int &c) { contentFilter = c; }
32
33 bool parseVYMHandler::startDocument()
34 {
35     errorProt = "";
36     state = StateInit;
37     stateStack.clear();
38     stateStack.append(StateInit);
39     htmldata = "";
40     isVymPart = false;
41     useProgress = false;
42     return true;
43 }
44
45 bool parseVYMHandler::startElement(const QString &, const QString &,
46                                    const QString &eName,
47                                    const QXmlAttributes &atts)
48 {
49     QColor col;
50     /* Testing
51     qDebug() << "startElement: <" << eName
52              << ">     state=" << state
53              << "  laststate=" << stateStack.last()
54              << "   loadMode=" << loadMode
55             //<<"       line=" << QXmlDefaultHandler::lineNumber();
56              << "contentFilter=" << contentFilter;
57     */
58
59     stateStack.append(state);
60     if (state == StateInit && (eName == "vymmap")) {
61         state = StateMap;
62         branchesTotal = 0;
63         branchesCounter = 0;
64
65         if (loadMode == NewMap || loadMode == DefaultMap) {
66             // Create mapCenter
67             model->clear();
68             lastBranch = NULL;
69
70             readMapAttr(atts);
71         }
72         // Check version
73         if (!atts.value("version").isEmpty()) {
74             version = atts.value("version");
75             if (!versionLowerOrEqualThanVym(version)) {
76                 QMessageBox::warning(
77                     0, QObject::tr("Warning: Version Problem"),
78                     QObject::tr(
79                         "<h3>Map is newer than VYM</h3>"
80                         "<p>The map you are just trying to load was "
81                         "saved using vym %1. "
82                         "The version of this vym is %2. "
83                         "If you run into problems after pressing "
84                         "the ok-button below, updating vym should help.</p>")
85                         .arg(version)
86                         .arg(vymVersion) +
87                     QObject::tr(
88                         "<p>The map will be opened readonly, because not "
89                         "all information from new maps can be saved with this "
90                         "version of vym. Please be careful!"));
91                 model->setReadOnly(true);
92             }
93             else
94                 model->setVersion(version);
95         }
96     }
97     else if (eName == "mapdesign" && state == StateMap) {
98         state = StateMapDesign;
99     }
100     else if (eName == "md" && state == StateMapDesign) {
101         state = StateMD;
102         readMapDesignCompatibleAttr(atts);
103     }
104     else if (eName == "select" && state == StateMap) {
105         state = StateMapSelect;
106     }
107     else if (eName == "setting" && state == StateMap) {
108         state = StateMapSetting;
109         if (loadMode == NewMap) {
110             htmldata.clear();
111             readSettingAttr(atts);
112         }
113     }
114     else if (eName == "slide" && state == StateMap) {
115         state = StateMapSlide;
116         if (!(contentFilter & SlideContent)) {
117             // Ignore slides during paste
118             lastSlide = model->addSlide();
119             if (insertPos >= 0)
120                 model->relinkSlide(lastSlide, insertPos);
121
122             readSlideAttr(atts);
123         }
124     }
125     else if (eName == "mapcenter" && state == StateMap) {
126         state = StateMapCenter;
127         if (loadMode == NewMap) {
128             // Really use the found mapcenter as MCO in a new map
129             lastBranch = model->createMapCenter();
130         }
131         else {
132             // Treat the found mapcenter as a branch
133             // in an existing map
134             BranchItem *bi = model->getSelectedBranch();
135             if (bi) {
136                 lastBranch = bi;
137                 if (loadMode == ImportAdd) {
138                     // Import Add
139                     if (insertPos < 0)
140                         lastBranch = model->createBranch(lastBranch);
141                     else {
142                         lastBranch = model->addNewBranch(lastBranch, insertPos);
143                         insertPos++;
144                     }
145                 }
146                 else {
147                     // Import Replace
148                     if (insertPos < 0) {
149                         insertPos = lastBranch->num() + 1;
150                         model->clearItem(lastBranch);
151                     }
152                     else {
153                         BranchItem *pi = bi->parentBranch();
154                         lastBranch = model->addNewBranch(pi, insertPos);
155                         insertPos++;
156                     }
157                 }
158             }
159             else
160                 // if nothing selected, add mapCenter without parent
161                 lastBranch = model->createMapCenter();
162         }
163         readBranchAttr(atts);
164     }
165     else if ((eName == "standardflag" || eName == "standardFlag") &&
166              (state == StateMapCenter || state == StateBranch)) {
167         state = StateStandardFlag;
168     }
169     else if (eName == "userflagdef" && state == StateMap) {
170         state = StateUserFlagDef;
171         return (readUserFlagDefAttr(atts));
172     }
173     else if (eName == "userflag" &&
174              (state == StateMapCenter || state == StateBranch)) {
175         state = StateUserFlag;
176         return (readUserFlagAttr(atts));
177     }
178     else if (eName == "heading" &&
179              (state == StateMapCenter || state == StateBranch ||
180               state == StateInit)) {
181         if (state == StateInit) {
182             // Only read some stuff like VymNote or Heading
183             // e.g. for undo/redo
184             lastBranch = model->getSelectedBranch();
185             if (version.isEmpty())
186                 version = "0.0.0";
187         }
188         if (!lastBranch)
189             return false;
190
191         state = StateHeading;
192         htmldata.clear();
193         vymtext.clear();
194         if (!atts.value("fonthint").isEmpty())
195             vymtext.setFontHint(atts.value("fonthint"));
196         if (!atts.value("textMode").isEmpty()) {
197             if (atts.value("textMode") == "richText")
198                 vymtext.setRichText(true);
199             else
200                 vymtext.setRichText(false);
201         }
202         if (!atts.value("textColor").isEmpty()) {
203             // For compatibility with <= 2.4.0 set both branch and
204             // heading color
205             col.setNamedColor(atts.value("textColor"));
206             lastBranch->setHeadingColor(col);
207             vymtext.setColor(col);
208         }
209         if (!atts.value("text").isEmpty())
210             vymtext.setText(unquoteQuotes(atts.value("text")));
211     }
212     else if (eName == "task" &&
213              (state == StateMapCenter || state == StateBranch)) {
214         state = StateTask;
215         lastTask = taskModel->createTask(lastBranch);
216         if (!readTaskAttr(atts))
217             return false;
218     }
219     else if (eName == "note" &&
220              (state == StateMapCenter ||
221               state == StateBranch)) { // only for backward compatibility
222                                        // (<1.4.6). Use htmlnote now.
223         state = StateNote;
224         htmldata.clear();
225         vymtext.clear();
226         if (!readNoteAttr(atts))
227             return false;
228     }
229     else if (eName == "htmlnote" &&
230              state == StateMapCenter) { // only for backward compatibility. Use
231                                         // vymnote now
232         state = StateHtmlNote;
233         vymtext.clear();
234         if (!atts.value("fonthint").isEmpty())
235             vymtext.setFontHint(atts.value("fonthint"));
236     }
237     else if (eName == "vymnote" &&
238              (state == StateMapCenter || state == StateBranch ||
239               state == StateInit)) {
240         if (state == StateInit)
241         // Only read some stuff like VymNote or Heading
242         // e.g. for undo/redo
243         {
244             lastBranch = model->getSelectedBranch();
245             if (version.isEmpty())
246                 version = "0.0.0";
247         }
248         state = StateVymNote;
249         htmldata.clear();
250         vymtext.clear();
251         if (!atts.value("fonthint").isEmpty())
252             vymtext.setFontHint(atts.value("fonthint"));
253         if (!atts.value("textMode").isEmpty()) {
254             if (atts.value("textMode") == "richText")
255                 vymtext.setRichText(true);
256             else
257                 vymtext.setRichText(false);
258         }
259         if (!atts.value("text").isEmpty())
260             vymtext.setText(unquoteQuotes(atts.value("text")));
261     }
262     else if (eName == "floatimage" &&
263              (state == StateMapCenter || state == StateBranch)) {
264         state = StateImage;
265         lastImage = model->createImage(lastBranch);
266         if (!readImageAttr(atts))
267             return false;
268     }
269     else if ((eName == "branch" || eName == "floatimage") &&
270              state == StateMap) {
271         // This is used in vymparts, which have no mapcenter or for undo
272         isVymPart = true;
273         TreeItem *ti = model->getSelectedItem();
274         if (!ti) {
275             // If a vym part is _loaded_ (not imported),
276             // selection==lmo==NULL
277             // Treat it like ImportAdd then...
278             loadMode = ImportAdd;
279             // we really have no MCO at this time
280             lastBranch = model->createMapCenter();
281             model->select(lastBranch);
282             model->setHeadingPlainText("Import");
283             ti = lastBranch;
284         }
285         if (ti && ti->isBranchLikeType()) {
286             lastBranch = (BranchItem *)ti;
287             if (eName == "branch") {
288                 state = StateBranch;
289                 if (loadMode == ImportAdd) {
290                     lastBranch = model->createBranch(lastBranch);
291                     model->setLatestAddedItem(lastBranch);
292                     if (insertPos >= 0)
293                         model->relinkBranch(lastBranch, (BranchItem *)ti,
294                                             insertPos);
295                 }
296                 else
297                     model->clearItem(lastBranch);
298                 readBranchAttr(atts);
299             }
300             else if (eName == "floatimage") {
301                 state = StateImage;
302                 lastImage = model->createImage(lastBranch);
303                 model->setLatestAddedItem(lastImage);
304                 if (!readImageAttr(atts))
305                     return false;
306             }
307             else
308                 return false;
309         }
310         else
311             return false;
312     }
313     else if (eName == "branch" && state == StateMapCenter) {
314         state = StateBranch;
315         lastBranch = model->createBranch(lastBranch);
316         readBranchAttr(atts);
317     }
318     else if (eName == "htmlnote" &&
319              state == StateBranch) { // only for backward compatibility. Use
320                                      // vymnote now
321         state = StateHtmlNote;
322         vymtext.clear();
323         if (!atts.value("fonthint").isEmpty())
324             vymtext.setFontHint(atts.value("fonthint"));
325     }
326     else if (eName == "frame" &&
327              (state == StateBranch || state == StateMapCenter)) {
328         state = StateFrame;
329         if (!readFrameAttr(atts))
330             return false;
331     }
332     else if (eName == "xlink" && state == StateBranch) {
333         // Obsolete after 1.13.2
334         state = StateBranchXLink;
335         if (!readXLinkAttr(atts))
336             return false;
337     }
338     else if (eName == "xlink" && state == StateMap) {
339         state = StateLink;
340         if (!readLinkNewAttr(atts))
341             return false;
342     }
343     else if (eName == "branch" && state == StateBranch) {
344         lastBranch = model->createBranch(lastBranch);
345         readBranchAttr(atts);
346     }
347     else if (eName == "html" &&
348              (state == StateHtmlNote ||
349               state == StateNote ||
350               state == StateVymNote)) { // Only for backward compatibility
351         state = StateHtml;
352         htmldata = "<" + eName;
353         readHtmlAttr(atts);
354         htmldata += ">";
355     }
356     else if (eName == "attribute" &&
357              (state == StateBranch || state == StateMapCenter)) {
358         state = StateAttribute;
359         AttributeItem *ai = new AttributeItem(lastBranch);
360         if (ai) {
361             if (!atts.value("key").isEmpty())
362                 ai->setKey(atts.value("key"));
363
364             QString type = atts.value("type");
365             QString val = atts.value("value");
366             if (!type.isEmpty()) {
367                 if (type == "Integer")
368                     ai->setValue(val.toInt());
369                 else if (type == "String")
370                     ai->setValue(val);
371                 else if (type == "Undefined") {
372                     ai->setValue(val);
373                     ai->setAttributeType(AttributeItem::Undefined);
374                     qWarning() << "Found attribute type 'Undefined'";
375                 } else if (type == "DateTime") {
376                     ai->setValue(QDateTime::fromString(val, Qt::ISODate));
377                 } else
378                     qWarning() << "Found unknown attribute type: " << type;
379             } else {
380                 if (!atts.value("value").isEmpty())
381                     ai->setValue(atts.value("value"));
382             }
383         }
384         model->setAttribute(lastBranch, ai);
385     }
386     else if (state == StateHtml) {
387         // Only for backward compatibility
388         // accept all while in html mode,
389         htmldata += "<" + eName;
390         readHtmlAttr(atts);
391         htmldata += ">";
392     }
393     else
394         return false; // Error
395     return true;
396 }
397
398 bool parseVYMHandler::endElement(const QString &, const QString &,
399                                  const QString &eName)
400 {
401     /* Testing
402     QString h;
403     lastBranch ? h = lastBranch->getHeadingPlain() : h = "";
404     qDebug() << "endElement </" << eName << ">  state=" << state << " lastBranch=" << h;
405     */
406
407     switch (state) {
408     case StateMap:
409     case StateMapDesign:
410     case StateMD:
411         break;
412     case StateMapCenter:
413         model->emitDataChanged(lastBranch);
414         lastBranch = (BranchItem *)(lastBranch->parent());
415         break;
416     case StateBranch:
417         // Empty branches may not be scrolled
418         // (happens if bookmarks are imported)
419         if (lastBranch->isScrolled() && lastBranch->branchCount() == 0)
420             lastBranch->unScroll();
421
422         model->emitDataChanged(lastBranch);
423         lastBranch = (BranchItem *)(lastBranch->parent());
424         lastBranch->setLastSelectedBranch(0);
425         break;
426     case StateTask:
427         break;
428     case StateHeading:
429         if (versionLowerOrEqual(version, "2.4.99") &&
430             htmldata.contains("<html>"))
431             // versions before 2.5.0 didn't use CDATA to save richtext
432             vymtext.setAutoText(htmldata);
433         else {
434             // Versions 2.5.0 to 2.7.562  had HTML data encoded as CDATA
435             // Later versions use the <vymnote  text="...">  attribute,
436             // which is set already in begin element
437             // If both htmldata and vymtext are already available, use the
438             // vymtext
439             if (vymtext.isEmpty())
440                 vymtext.setText(htmldata);
441         }
442         lastBranch->setHeading(vymtext);
443         break;
444     case StateHtmlNote: // Richtext note, needed anyway for backward
445                         // compatibility
446         if (htmldata.contains("<html"))
447             vymtext.setRichText(htmldata);
448         else
449             vymtext.setPlainText(htmldata);
450         lastBranch->setNote(vymtext);
451         break;
452     case StateMapSlide:
453         lastSlide = NULL;
454         break;
455     case StateNote:
456         // version < 1.4.6
457         if (!htmldata.isEmpty()) {
458             if (htmldata.contains("<html"))
459                 vymtext.setRichText(htmldata);
460             else
461                 vymtext.setPlainText(htmldata);
462         }
463         lastBranch->setNote(vymtext);
464         break;
465     case StateMapSetting:
466         // version >= 2.5.0  previously value only as attribut
467         settings.setLocalValue(model->getDestPath(), lastSetting, htmldata);
468         break;
469     case StateVymNote: // Might be richtext or plaintext with
470         // version >= 1.13.8
471         if (versionLowerOrEqual(version, "2.4.99") &&
472             htmldata.contains("<html>"))
473             // versions before 2.5.0 didn't use CDATA to save richtext
474             vymtext.setAutoText(htmldata);
475         else {
476             // Versions 2.5.0 to 2.7.562  had HTML data encoded as CDATA
477             // Later versions use the <vymnote  text="...">  attribute,
478             // which is set already in begin element
479             // If both htmldata and vymtext are already available, use the
480             // vymtext
481             if (vymtext.isEmpty())
482                 vymtext.setText(htmldata);
483         }
484         lastBranch->setNote(vymtext);
485         break;
486     case StateHtml:
487         htmldata += "</" + eName + ">";
488         if (eName == "html")
489             htmldata.replace("<br></br>", "<br />");
490         break;
491     default:
492         break;
493     }
494     state = stateStack.takeLast();
495     return true;
496 }
497
498 bool parseVYMHandler::characters(const QString &ch)
499 {
500     // qDebug()<< "xml-vym: characters " << ch << "  state=" << state;
501
502     QString ch_org = quoteMeta(ch);
503     QString ch_simplified = ch.simplified();
504
505     switch (state) {
506     case StateInit:
507     case StateMap:
508     case StateMapDesign:
509     case StateMD:
510         break;
511     case StateMapSelect:
512         model->select(ch_simplified);
513         break;
514     case StateMapSetting:
515         htmldata += ch;
516         break;
517     case StateMapCenter:
518         break;
519     case StateNote: // only in vym <1.4.6
520         htmldata += ch_simplified;
521         break;
522     case StateBranch:
523         break;
524     case StateStandardFlag:
525         lastBranch->activateStandardFlagByName(ch_simplified);
526         break;
527     case StateImage:
528         break;
529     case StateVymNote:
530         htmldata += ch;
531         break;
532     case StateHtmlNote: // Only for compatibility
533         htmldata += ch;
534         break;
535     case StateHtml:
536         htmldata += ch_simplified;
537         break;
538     case StateHeading:
539         htmldata += ch;
540         break;
541     default:
542         return false;
543     }
544     return true;
545 }
546
547 QString parseVYMHandler::errorString()
548 {
549     return "the document is not in the VYM file format";
550 }
551
552 bool parseVYMHandler::readMapAttr( const QXmlAttributes &a)
553 {
554     if (!a.value("author").isEmpty())
555         model->setAuthor(a.value("author"));
556     if (!a.value("title").isEmpty())
557         model->setTitle(a.value("title"));
558     if (!a.value("comment").isEmpty())
559         model->setComment(unquoteMeta(a.value("comment")));
560     if (!a.value("branchCount").isEmpty()) {
561         branchesTotal = a.value("branchCount").toInt();
562         if (branchesTotal > 10) {
563             useProgress = true;
564             mainWindow->setProgressMaximum(branchesTotal);
565         }
566     }
567
568     if (!a.value("mapZoomFactor").isEmpty())
569         model->setMapZoomFactor(a.value("mapZoomFactor").toDouble());
570     if (!a.value("mapRotationAngle").isEmpty())
571         model->setMapRotationAngle(a.value("mapRotationAngle").toDouble());
572
573     return readMapDesignCompatibleAttr(a);
574 }
575
576 bool parseVYMHandler::readMapDesignCompatibleAttr( const QXmlAttributes &a)
577 {
578     // Some attributes moved in version 2.9.514 from
579     // <vymmap> to <mapdesign> and <md>
580     // This code here will allow to parse also newer maps.
581     // Some elements though are not available in older versions, especially
582     // <frame frameUsage="outerFrame" ...>
583     // <branch rotHeading=... rotContent=... >
584
585     QColor col;
586     if (!a.value("backgroundColor").isEmpty()) {
587         col.setNamedColor(a.value("backgroundColor"));
588         model->setMapBackgroundColor(col);
589     }
590     if (!a.value("defaultFont").isEmpty()) {
591         QFont font;
592         font.fromString(a.value("defaultFont"));
593         model->setMapDefaultFont(font);
594     }
595     if (!a.value("selectionColor").isEmpty()) {
596         // Only for compatibility
597         col.setNamedColor(a.value("selectionColor"));
598         model->setSelectionBrushColor(col);
599         model->setSelectionPenColor(col);
600         model->setSelectionPenWidth(1);
601     }
602     if (!a.value("selectionPenColor").isEmpty()) {
603         // Introduced in 2.9.12
604         col.setNamedColor(a.value("selectionPenColor"));
605         model->setSelectionPenColor(col);
606     }
607     if (!a.value("selectionPenWidth").isEmpty()) {
608         // Introduced in 2.9.12
609         bool ok;
610         qreal w = a.value("selectionPenWidth").toFloat(&ok);
611         if (ok)
612             model->setSelectionPenWidth(w);
613     }
614     if (!a.value("selectionBrushColor").isEmpty()) {
615         // Introduced in 2.9.12
616         col.setNamedColor(a.value("selectionBrushColor"));
617         model->setSelectionBrushColor(col);
618     }
619     if (!a.value("linkColorHint").isEmpty()) {
620         if (a.value("linkColorHint") == "HeadingColor")
621             model->setMapLinkColorHint(LinkableMapObj::HeadingColor);
622         else
623             model->setMapLinkColorHint(LinkableMapObj::DefaultColor);
624     }
625     if (!a.value("linkStyle").isEmpty())
626         model->setMapLinkStyle(a.value("linkStyle"));
627     if (!a.value("linkColor").isEmpty()) {
628         col.setNamedColor(a.value("linkColor"));
629         model->setMapDefLinkColor(col);
630     }
631
632     QPen pen(model->getMapDefXLinkPen());
633     if (!a.value("defXLinkColor").isEmpty()) {
634         col.setNamedColor(a.value("defXLinkColor"));
635         pen.setColor(col);
636     }
637     if (!a.value("defXLinkWidth").isEmpty())
638         pen.setWidth(a.value("defXLinkWidth").toInt());
639     if (!a.value("defXLinkPenStyle").isEmpty()) {
640         bool ok;
641         Qt::PenStyle ps = penStyle(a.value("defXLinkPenStyle"), ok);
642         if (!ok)
643             return false;
644         pen.setStyle(ps);
645     }
646     model->setMapDefXLinkPen(pen);
647
648     if (!a.value("defXLinkStyleBegin").isEmpty())
649         model->setMapDefXLinkStyleBegin(a.value("defXLinkStyleBegin"));
650     if (!a.value("defXLinkStyleEnd").isEmpty())
651         model->setMapDefXLinkStyleEnd(a.value("defXLinkStyleEnd"));
652     return true;
653 }
654
655 bool parseVYMHandler::readBranchAttr(const QXmlAttributes &a)
656 {
657     branchesCounter++;
658     if (useProgress)
659         mainWindow->addProgressValue((float)branchesCounter / branchesTotal);
660
661     lastMI = lastBranch;
662
663     if (!readOOAttr(a))
664         return false;
665
666     if (!a.value("scrolled").isEmpty())
667         lastBranch->toggleScroll();
668
669     if (!a.value("incImgV").isEmpty()) {
670         if (a.value("incImgV") == "true")
671             lastBranch->setIncludeImagesVer(true);
672         else
673             lastBranch->setIncludeImagesVer(false);
674     }
675     if (!a.value("incImgH").isEmpty()) {
676         if (a.value("incImgH") == "true")
677             lastBranch->setIncludeImagesHor(true);
678         else
679             lastBranch->setIncludeImagesHor(false);
680     }
681     if (a.value("childrenFreePos") == "true")
682         lastBranch->setChildrenLayout(BranchItem::FreePositioning);
683
684     return true;
685 }
686
687 bool parseVYMHandler::readFrameAttr(const QXmlAttributes &a)
688 {
689     if (lastMI) {
690         OrnamentedObj *oo = (OrnamentedObj *)(lastMI->getLMO());
691         if (oo) {
692             bool ok;
693             int x;
694             {
695                 if (!a.value("frameType").isEmpty())
696                     oo->setFrameType(a.value("frameType"));
697                 if (!a.value("penColor").isEmpty())
698                     oo->setFramePenColor(a.value("penColor"));
699                 if (!a.value("brushColor").isEmpty()) {
700                     oo->setFrameBrushColor(a.value("brushColor"));
701                     lastMI->setBackgroundColor(a.value("brushColor"));
702                 }
703                 if (!a.value("padding").isEmpty()) {
704                     x = a.value("padding").toInt(&ok);
705                     if (ok)
706                         oo->setFramePadding(x);
707                 }
708                 if (!a.value("borderWidth").isEmpty()) {
709                     x = a.value("borderWidth").toInt(&ok);
710                     if (ok)
711                         oo->setFrameBorderWidth(x);
712                 }
713                 if (!a.value("includeChildren").isEmpty()) {
714                     if (a.value("includeChildren") == "true")
715                         oo->setFrameIncludeChildren(true);
716                     else
717                         oo->setFrameIncludeChildren(false);
718                 }
719             }
720             return true;
721         }
722     }
723     return false;
724 }
725
726 bool parseVYMHandler::readOOAttr(const QXmlAttributes &a)
727 {
728     if (lastMI) {
729         bool okx, oky;
730         float x, y;
731         if (!a.value("posX").isEmpty()) {   // Introduced in 2.9.501, added here for file compatibility
732             if (!a.value("posY").isEmpty()) {
733                 x = a.value("posX").toFloat(&okx);
734                 y = a.value("posY").toFloat(&oky);
735                 if (okx && oky)
736                     lastMI->setRelPos(QPointF(x, y));
737                 else
738                     return false; // Couldn't read relPos
739             }
740         }
741         if (!a.value("relPosX").isEmpty()) {
742             if (!a.value("relPosY").isEmpty()) {
743                 x = a.value("relPosX").toFloat(&okx);
744                 y = a.value("relPosY").toFloat(&oky);
745                 if (okx && oky)
746                     lastMI->setRelPos(QPointF(x, y));
747                 else
748                     return false; // Couldn't read relPos
749             }
750         }
751         if (!a.value("absPosX").isEmpty()) {
752             if (!a.value("absPosY").isEmpty()) {
753                 x = a.value("absPosX").toFloat(&okx);
754                 y = a.value("absPosY").toFloat(&oky);
755                 if (okx && oky)
756                     lastMI->setAbsPos(QPointF(x, y));
757                 else
758                     return false; // Couldn't read absPos
759             }
760         }
761         if (!a.value("url").isEmpty())
762             lastMI->setURL(a.value("url"));
763         if (!a.value("vymLink").isEmpty())
764             lastMI->setVymLink(a.value("vymLink"));
765         if (!a.value("hideInExport").isEmpty())
766             if (a.value("hideInExport") == "true")
767                 lastMI->setHideInExport(true);
768
769         if (!a.value("hideLink").isEmpty()) {
770             if (a.value("hideLink") == "true")
771                 lastMI->setHideLinkUnselected(true);
772             else
773                 lastMI->setHideLinkUnselected(false);
774         }
775
776         if (!a.value("localTarget").isEmpty())
777             if (a.value("localTarget") == "true")
778                 lastMI->toggleTarget();
779         if (!a.value("rotation").isEmpty()) {
780             x = a.value("rotation").toFloat(&okx);
781             if (okx)
782                 lastMI->setRotation(x);
783             else
784                 return false; // Couldn't read rotation
785         }
786
787         if (!a.value("uuid").isEmpty()) {
788             // While pasting, check for existing UUID
789             if (loadMode != ImportAdd && !model->findUuid(a.value("uuid")))
790                 lastMI->setUuid(a.value("uuid"));
791         }
792     }
793     return true;
794 }
795
796 bool parseVYMHandler::readNoteAttr(const QXmlAttributes &a)
797 { // only for backward compatibility (<1.4.6). Use htmlnote now.
798     vymtext.clear();
799     QString fn;
800     if (!a.value("href").isEmpty()) {
801         // Load note
802         fn = parseHREF(a.value("href"));
803         QFile file(fn);
804         QString s; // Reading a note
805
806         if (!file.open(QIODevice::ReadOnly)) {
807             qWarning() << "parseVYMHandler::readNoteAttr:  Couldn't load " + fn;
808             return false;
809         }
810         QTextStream stream(&file);
811         stream.setCodec("UTF-8");
812         QString lines;
813         while (!stream.atEnd()) {
814             lines += stream.readLine() + "\n";
815         }
816         file.close();
817
818         if (lines.contains("<html")) {
819             vymtext.setRichText(lines);
820         } else
821             vymtext.setPlainText(lines);
822     }
823     if (!a.value("fonthint").isEmpty())
824         vymtext.setFontHint(a.value("fonthint"));
825     return true;
826 }
827
828 bool parseVYMHandler::readImageAttr(const QXmlAttributes &a)
829 {
830     lastMI = lastImage;
831
832     if (!readOOAttr(a))
833         return false;
834
835     if (!a.value("href").isEmpty()) {
836         // Load Image
837         if (!lastImage->load(parseHREF(a.value("href")))) {
838             QMessageBox::warning(0, "Warning: ",
839                                  "Couldn't load image\n" +
840                                      parseHREF(a.value("href")));
841             lastImage = NULL;
842             return true;
843         }
844     }
845     if (!a.value("zPlane").isEmpty())
846         lastImage->setZValue(a.value("zPlane").toInt());
847     float x, y;
848     bool okx, oky;
849     if (!a.value("posX").isEmpty()) {   // Introduced in 2.9.501, added here for file compatibility
850         if (!a.value("posY").isEmpty()) {
851             x = a.value("posX").toFloat(&okx);
852             y = a.value("posY").toFloat(&oky);
853             if (okx && oky)
854                 lastImage->setRelPos(QPointF(x, y));
855             else
856                 return false; // Couldn't read relPos
857         }
858     }
859     if (!a.value("relPosX").isEmpty()) {
860         if (!a.value("relPosY").isEmpty()) {
861             // read relPos
862             x = a.value("relPosX").toFloat(&okx);
863             y = a.value("relPosY").toFloat(&oky);
864             if (okx && oky)
865                 lastImage->setRelPos(QPointF(x, y));
866             else
867                 // Couldn't read relPos
868                 return false;
869         }
870     }
871
872     // Scale image
873     // scaleX and scaleY are no longer used since 2.7.509 and replaced by
874     // scaleFactor
875     x = y = 1;
876     if (!a.value("scaleX").isEmpty()) {
877         x = a.value("scaleX").toFloat(&okx);
878         if (!okx)
879             return false;
880     }
881
882     if (!a.value("scaleY").isEmpty()) {
883         x = a.value("scaleY").toFloat(&oky);
884         if (!oky)
885             return false;
886     }
887
888     if (!a.value("scaleFactor").isEmpty()) {
889         x = a.value("scaleFactor").toFloat(&okx);
890         if (!okx)
891             return false;
892     }
893
894     if (x != 1)
895         lastImage->setScaleFactor(x);
896
897     if (!readOOAttr(a))
898         return false;
899
900     if (!a.value("originalName").isEmpty())
901     {
902         lastImage->setOriginalFilename(a.value("originalName"));
903     }
904     return true;
905 }
906
907 bool parseVYMHandler::readXLinkAttr(const QXmlAttributes &a)
908 {
909     // Obsolete, see also readLinkAttr
910
911     if (!a.value("beginID").isEmpty()) {
912         if (!a.value("endID").isEmpty()) {
913             TreeItem *beginBI = model->findBySelectString(a.value("beginID"));
914             TreeItem *endBI = model->findBySelectString(a.value("endID"));
915             if (beginBI && endBI && beginBI->isBranchLikeType() &&
916                 endBI->isBranchLikeType()) {
917                 Link *li = new Link(model);
918                 li->setBeginBranch((BranchItem *)beginBI);
919                 li->setEndBranch((BranchItem *)endBI);
920                 QPen pen = li->getPen();
921
922                 if (!a.value("color").isEmpty()) {
923                     QColor col;
924                     col.setNamedColor(a.value("color"));
925                     pen.setColor(col);
926                 }
927
928                 if (!a.value("width").isEmpty()) {
929                     bool okx;
930                     pen.setWidth(a.value("width").toInt(&okx, 10));
931                 }
932                 li->setPen(pen);
933                 model->createLink(li);
934             }
935         }
936     }
937     return true;
938 }
939
940 bool parseVYMHandler::readLinkNewAttr(const QXmlAttributes &a)
941 {
942     // object ID is used starting in version 1.8.76
943     // (before there was beginBranch and endBranch)
944     //
945     // Starting in 1.13.2 xlinks are no longer subitems of branches,
946     // but listed at the end of the data in a map. This makes handling
947     // of links much safer and easier
948
949     if (!a.value("beginID").isEmpty()) {
950         if (!a.value("endID").isEmpty()) {
951             TreeItem *beginBI = model->findBySelectString(a.value("beginID"));
952             TreeItem *endBI = model->findBySelectString(a.value("endID"));
953             if (beginBI && endBI && beginBI->isBranchLikeType() &&
954                 endBI->isBranchLikeType()) {
955                 Link *li = new Link(model);
956                 li->setBeginBranch((BranchItem *)beginBI);
957                 li->setEndBranch((BranchItem *)endBI);
958
959                 model->createLink(li);
960
961                 bool okx;
962                 QPen pen = li->getPen();
963                 if (!a.value("type").isEmpty()) {
964                     li->setLinkType(a.value("type"));
965                 }
966                 if (!a.value("color").isEmpty()) {
967                     QColor col;
968                     col.setNamedColor(a.value("color"));
969                     pen.setColor(col);
970                 }
971                 if (!a.value("width").isEmpty()) {
972                     pen.setWidth(a.value("width").toInt(&okx, 10));
973                 }
974                 if (!a.value("penstyle").isEmpty()) {
975                     pen.setStyle(penStyle(a.value("penstyle"), okx));
976                 }
977                 li->setPen(pen);
978
979                 if (!a.value("styleBegin").isEmpty())
980                     li->setStyleBegin(a.value("styleBegin"));
981                 if (!a.value("styleEnd").isEmpty())
982                     li->setStyleEnd(a.value("styleEnd"));
983
984                 XLinkObj *xlo = (XLinkObj *)(li->getMO());
985                 if (xlo && !a.value("c0").isEmpty()) {
986                     QPointF p = point(a.value("c0"), okx);
987                     if (okx)
988                         xlo->setC0(p);
989                 }
990                 if (xlo && !a.value("c1").isEmpty()) {
991                     QPointF p = point(a.value("c1"), okx);
992                     if (okx)
993                         xlo->setC1(p);
994                 }
995             }
996         }
997     }
998     return true;
999 }
1000
1001 bool parseVYMHandler::readSettingAttr(const QXmlAttributes &a)
1002 {
1003     if (!a.value("key").isEmpty()) {
1004         lastSetting = a.value("key");
1005         if (!a.value("value").isEmpty())
1006             settings.setLocalValue(
1007                     model->getDestPath(), a.value("key"),
1008                     a.value("value"));
1009         else
1010             return false;
1011     }
1012     else
1013         return false;
1014
1015     return true;
1016 }
1017
1018 bool parseVYMHandler::readSlideAttr(const QXmlAttributes &a)
1019 {
1020     QStringList scriptlines; // FIXME-3 needed for switching to inScript
1021                              // Most attributes are obsolete with inScript
1022     if (!lastSlide)
1023         return false;
1024     {
1025         if (!a.value("name").isEmpty())
1026             lastSlide->setName(a.value("name"));
1027         if (!a.value("zoom").isEmpty()) {
1028             bool ok;
1029             qreal z = a.value("zoom").toDouble(&ok);
1030             if (!ok)
1031                 return false;
1032             scriptlines.append(QString("setMapZoom(%1)").arg(z));
1033         }
1034         if (!a.value("rotation").isEmpty()) {
1035             bool ok;
1036             qreal z = a.value("rotation").toDouble(&ok);
1037             if (!ok)
1038                 return false;
1039             scriptlines.append(QString("setMapRotation(%1)").arg(z));
1040         }
1041         if (!a.value("duration").isEmpty()) {
1042             bool ok;
1043             int d = a.value("duration").toInt(&ok);
1044             if (!ok)
1045                 return false;
1046             scriptlines.append(QString("setMapAnimDuration(%1)").arg(d));
1047         }
1048         if (!a.value("curve").isEmpty()) {
1049             bool ok;
1050             int i = a.value("curve").toInt(&ok);
1051             if (!ok)
1052                 return false;
1053             if (i < 0 || i > QEasingCurve::OutInBounce)
1054                 return false;
1055             scriptlines.append(QString("setMapAnimCurve(%1)").arg(i));
1056         }
1057         if (!a.value("mapitem").isEmpty()) {
1058             TreeItem *ti = model->findBySelectString(a.value("mapitem"));
1059             if (!ti)
1060                 return false;
1061             scriptlines.append(
1062                 QString("centerOnID(\"%1\")").arg(ti->getUuid().toString()));
1063         }
1064         if (!a.value("inScript").isEmpty()) {
1065             lastSlide->setInScript(unquoteMeta(a.value("inScript")));
1066         }
1067         else
1068             lastSlide->setInScript(unquoteMeta(scriptlines.join(";\n")));
1069
1070         if (!a.value("outScript").isEmpty()) {
1071             lastSlide->setOutScript(unquoteMeta(a.value("outScript")));
1072         }
1073     }
1074     return true;
1075 }
1076
1077 bool parseVYMHandler::readTaskAttr(const QXmlAttributes &a)
1078 {
1079     if (!lastTask)
1080         return false;
1081     {
1082         if (!a.value("status").isEmpty())
1083             lastTask->setStatus(a.value("status"));
1084         if (!a.value("awake").isEmpty())
1085             lastTask->setAwake(a.value("awake"));
1086         if (!a.value("date_creation").isEmpty())
1087             lastTask->setDateCreation(a.value("date_creation"));
1088         if (!a.value("date_modification").isEmpty())
1089             lastTask->setDateModification(a.value("date_modification"));
1090         if (!a.value("date_sleep").isEmpty()) {
1091             if (!lastTask->setDateSleep(a.value("date_sleep")))
1092                 return false;
1093         }
1094         if (!a.value("prio_delta").isEmpty()) {
1095             lastTask->setPriorityDelta(a.value("prio_delta").toInt());
1096         }
1097     }
1098     return true;
1099 }
1100
1101 bool parseVYMHandler::readUserFlagDefAttr(const QXmlAttributes &a)
1102 {
1103     QString name;
1104     QString path;
1105     QString tooltip;
1106     QUuid uid;
1107
1108     if (!a.value("name").isEmpty())
1109         name = a.value("name");
1110     if (!a.value("tooltip").isEmpty())
1111         tooltip = a.value("tooltip");
1112     if (!a.value("uuid").isEmpty())
1113         uid = QUuid(a.value("uuid"));
1114
1115     Flag *flag;
1116
1117     if (!a.value("href").isEmpty()) {
1118         // Setup flag with image
1119         flag = mainWindow->setupFlag(parseHREF(a.value("href")), Flag::UserFlag,
1120                                      name, tooltip, uid);
1121     }
1122     else {
1123         qWarning() << "readUserFlagDefAttr:  Couldn't read href of flag "
1124                    << a.value("name");
1125         return false;
1126     }
1127
1128     if (!a.value("group").isEmpty())
1129         flag->setGroup(a.value("group"));
1130
1131     return true;
1132 }
1133
1134 bool parseVYMHandler::readUserFlagAttr(const QXmlAttributes &a)
1135 {
1136     QString name;
1137     QString uuid;
1138
1139     if (!a.value("name").isEmpty())
1140         name = a.value("name");
1141     if (!a.value("uuid").isEmpty())
1142         uuid = a.value("uuid");
1143
1144     lastBranch->toggleFlagByUid(QUuid(uuid));
1145
1146     return true;
1147 }