]> git.sven.stormbind.net Git - sven/vym.git/blob - src/vymmodel.cpp
Replace Pierre as the maintainer
[sven/vym.git] / src / vymmodel.cpp
1 #include <QApplication>
2 #include <QSvgGenerator>
3
4 #if defined(VYM_DBUS)
5 #include <QtDBus/QDBusConnection>
6 #endif
7
8 #ifndef Q_OS_WINDOWS
9 #include <unistd.h>
10 #else
11 #define sleep Sleep
12 #endif
13
14 #include <QColorDialog>
15 #include <QFileDialog>
16 #include <QMessageBox>
17 #include <QPrinter>
18
19 #include "vymmodel.h"
20
21 #include "attributeitem.h"
22 #include "branchitem.h"
23 #include "confluence-agent.h"
24 #include "download-agent.h"
25 #include "editxlinkdialog.h"
26 #include "export-ao.h"
27 #include "export-ascii.h"
28 #include "export-confluence.h"
29 #include "export-csv.h"
30 #include "export-firefox.h"
31 #include "export-html.h"
32 #include "export-impress.h"
33 #include "export-latex.h"
34 #include "export-markdown.h"
35 #include "export-orgmode.h"
36 #include "file.h"
37 #include "findresultmodel.h"
38 #include "jira-agent.h"
39 #include "lockedfiledialog.h"
40 #include "mainwindow.h"
41 #include "misc.h"
42 #include "noteeditor.h"
43 #include "options.h"
44 #include "scripteditor.h"
45 #include "slideitem.h"
46 #include "slidemodel.h"
47 #include "taskeditor.h"
48 #include "taskmodel.h"
49 #include "treeitem.h"
50 #include "vymprocess.h"
51 #include "warningdialog.h"
52 #include "xlinkitem.h"
53 #include "xlinkobj.h"
54 #include "xml-freemind.h"
55 #include "xml-vym.h"
56 #include "xmlobj.h"
57
58 #ifdef Q_OS_WINDOWS
59 #include <windows.h>
60 #endif
61
62 extern bool debug;
63 extern bool testmode;
64 extern bool restoreMode;
65 extern QStringList ignoredLockedFiles;
66
67 extern Main *mainWindow;
68
69 extern QDir tmpVymDir;
70
71 extern NoteEditor *noteEditor;
72 extern TaskEditor *taskEditor;
73 extern ScriptEditor *scriptEditor;
74 extern FlagRowMaster *standardFlagsMaster;
75 extern FlagRowMaster *userFlagsMaster;
76
77 extern Options options;
78
79 extern QString clipboardDir;
80 extern QString clipboardFile;
81
82 extern ImageIO imageIO;
83
84 extern TaskModel *taskModel;
85
86 extern QString vymName;
87 extern QString vymVersion;
88 extern QDir vymBaseDir;
89
90 extern QDir lastImageDir;
91 extern QDir lastMapDir;
92 extern QDir lastExportDir;
93
94 extern Settings settings;
95 extern QTextStream vout;
96
97 uint VymModel::idLast = 0; // make instance
98
99 VymModel::VymModel()
100 {
101     // qDebug()<< "Const VymModel" << this;
102     init();
103     rootItem->setModel(this);
104     wrapper = new VymModelWrapper(this);
105 }
106
107 VymModel::~VymModel()
108 {
109     // out << "Destr VymModel begin this="<<this<<"  "<<mapName<<flush;
110     mapEditor = nullptr;
111     repositionBlocked = true;
112     autosaveTimer->stop();
113     fileChangedTimer->stop();
114     stopAllAnimation();
115
116     // qApp->processEvents();   // Update view (scene()->update() is not enough)
117     // qDebug() << "Destr VymModel end   this="<<this;
118
119     vymLock.releaseLock();
120
121     delete (wrapper);
122 }
123
124 void VymModel::clear()
125 {
126     while (rootItem->childCount() > 0) {
127         // qDebug()<<"VM::clear  ri="<<rootItem<<"
128         // ri->count()="<<rootItem->childCount();
129         deleteItem(rootItem->getChildNum(0));
130     }
131 }
132
133 void VymModel::init()
134 {
135     // No MapEditor yet
136     mapEditor = NULL;
137
138     // Use default author
139     author =
140         settings
141             .value("/user/name", tr("unknown user",
142                                     "default name for map author in settings"))
143             .toString();
144
145     // States and IDs
146     idLast++;
147     modelID = idLast;
148     mapChanged = false;
149     mapDefault = true;
150     mapUnsaved = false;
151
152     // Selection history
153     selModel = NULL;
154     selectionBlocked = false;
155     resetSelectionHistory();
156
157     resetHistory();
158
159     // Create tmp dirs
160     makeTmpDirectories();
161
162     // Files
163     readonly = false;
164     zipped = true;
165     filePath = "";
166     fileName = tr("unnamed");
167     mapName = fileName;
168     repositionBlocked = false;
169     saveStateBlocked = false;
170
171     autosaveTimer = new QTimer(this);
172     connect(autosaveTimer, SIGNAL(timeout()), this, SLOT(autosave()));
173
174     fileChangedTimer = new QTimer(this);
175     connect(fileChangedTimer, SIGNAL(timeout()), this, SLOT(fileChanged()));
176     fileChangedTimer->start(3000);
177
178     taskAlarmTimer = new QTimer(this);
179     connect(taskAlarmTimer, SIGNAL(timeout()), this, SLOT(updateTasksAlarm()));
180     taskAlarmTimer->start(3000);
181
182     // animations   // FIXME-4 switch to new animation system
183     animationUse =
184         settings.value("/animation/use", false)
185             .toBool(); // FIXME-4 add options to control _what_ is animated
186     animationTicks = settings.value("/animation/ticks", 20).toInt();
187     animationInterval = settings.value("/animation/interval", 5).toInt();
188     animObjList.clear();
189     animationTimer = new QTimer(this);
190     connect(animationTimer, SIGNAL(timeout()), this, SLOT(animate()));
191
192     // View - map
193     defaultFont.setPointSizeF(16);
194     defLinkColor = QColor(0, 0, 255);
195     linkcolorhint = LinkableMapObj::DefaultColor;
196     linkstyle = LinkableMapObj::PolyParabel;
197     defXLinkPen.setWidth(1);
198     defXLinkPen.setColor(QColor(50, 50, 255));
199     defXLinkPen.setStyle(Qt::DashLine);
200     defXLinkStyleBegin = "HeadFull";
201     defXLinkStyleEnd = "HeadFull";
202
203     hasContextPos = false;
204
205     hidemode = TreeItem::HideNone;
206
207     // Animation in MapEditor
208     zoomFactor = 1;
209     rotationAngle = 0;
210     animDuration = 2000;
211     animCurve = QEasingCurve::OutQuint;
212
213     // Initialize presentation slides
214     slideModel = new SlideModel(this);
215     blockSlideSelection = false;
216
217     // Avoid recursions later
218     cleaningUpLinks = false;
219
220     // Network
221     netstate = Offline;
222
223 #if defined(VYM_DBUS)
224     // Announce myself on DBUS
225     new AdaptorModel(this); // Created and not deleted as documented in Qt
226     if (!QDBusConnection::sessionBus().registerObject(
227             QString("/vymmodel_%1").arg(modelID), this))
228         qWarning("VymModel: Couldn't register DBUS object!");
229 #endif
230 }
231
232 void VymModel::makeTmpDirectories()
233 {
234     // Create unique temporary directories
235     tmpMapDirPath = tmpVymDir.path() + QString("/model-%1").arg(modelID);
236     histPath = tmpMapDirPath + "/history";
237     QDir d;
238     d.mkdir(tmpMapDirPath);
239 }
240
241 QString VymModel::tmpDirPath() { return tmpMapDirPath; }
242
243 MapEditor *VymModel::getMapEditor() { return mapEditor; }
244
245 VymModelWrapper *VymModel::getWrapper() { return wrapper; }
246
247 bool VymModel::isRepositionBlocked() { return repositionBlocked; }
248
249 void VymModel::updateActions()
250 {
251     // Tell mainwindow to update states of actions
252     mainWindow->updateActions();
253 }
254
255 bool VymModel::setData(const QModelIndex &, const QVariant &value, int role)
256 {
257     if (role != Qt::EditRole)
258         return false;
259
260     setHeadingPlainText(value.toString());
261
262     return true;
263 }
264
265 void VymModel::resetUsedFlags()
266 {
267     standardFlagsMaster->resetUsedCounter();
268     userFlagsMaster->resetUsedCounter();
269 }
270
271 QString VymModel::saveToDir(const QString &tmpdir, const QString &prefix,
272                             FlagRowMaster::WriteMode flagMode, const QPointF &offset,
273                             TreeItem *saveSel)
274 {
275     // tmpdir       temporary directory to which data will be written
276     // prefix       mapname, which will be appended to images etc.
277     //
278     // writeflags   Only write flags for "real" save of map, not undo
279     // offset       offset of bbox of whole map in scene.
280     //              Needed for XML export
281
282     XMLObj xml;
283
284     // Save Header
285     QString ls;
286     switch (linkstyle) {
287     case LinkableMapObj::Line:
288         ls = "StyleLine";
289         break;
290     case LinkableMapObj::Parabel:
291         ls = "StyleParabel";
292         break;
293     case LinkableMapObj::PolyLine:
294         ls = "StylePolyLine";
295         break;
296     default:
297         ls = "StylePolyParabel";
298         break;
299     }
300
301     QString header =
302         "<?xml version=\"1.0\" encoding=\"utf-8\"?><!DOCTYPE vymmap>\n";
303     QString colhint = "";
304     if (linkcolorhint == LinkableMapObj::HeadingColor)
305         colhint = xml.attribut("linkColorHint", "HeadingColor");
306
307     QString mapAttr = xml.attribut("version", vymVersion);
308     if (!saveSel) {
309         QPen selPen = mapEditor->getSelectionPen();
310         QBrush selBrush = mapEditor->getSelectionBrush();
311
312         mapAttr +=
313             xml.attribut("author", author) + xml.attribut("title", title) +
314             xml.attribut("comment", comment) + xml.attribut("date", getDate()) +
315             xml.attribut("branchCount", QString().number(branchCount())) +
316             xml.attribut(
317                 "backgroundColor",
318                 mapEditor->getScene()->backgroundBrush().color().name()) +
319             xml.attribut("defaultFont", defaultFont.toString()) +
320             xml.attribut("selectionColor",  // FIXME-2 Only for compatibility until 2.9.513
321                          selBrush.color().name(QColor::HexArgb)) +
322             xml.attribut("selectionPenColor", selPen.color().name(QColor::HexArgb)) +
323             xml.attribut("selectionPenWidth", 
324                          QString().setNum(selPen.width())) + 
325             xml.attribut("selectionBrushColor", selBrush.color().name(QColor::HexArgb)) +
326             xml.attribut("linkStyle", ls) +
327             xml.attribut("linkColor", defLinkColor.name()) +
328             xml.attribut("defXLinkColor", defXLinkPen.color().name()) +
329             xml.attribut("defXLinkWidth",
330                          QString().setNum(defXLinkPen.width(), 10)) +
331             xml.attribut("defXLinkPenStyle",
332                          penStyleToString(defXLinkPen.style())) +
333             xml.attribut("defXLinkStyleBegin", defXLinkStyleBegin) +
334             xml.attribut("defXLinkStyleEnd", defXLinkStyleEnd) +
335             xml.attribut("mapZoomFactor",
336                          QString().setNum(mapEditor->getZoomFactorTarget())) +
337             xml.attribut("mapRotationAngle",
338                          QString().setNum(mapEditor->getAngleTarget())) +
339             colhint;
340     }
341     header += xml.beginElement("vymmap", mapAttr);
342     xml.incIndent();
343
344     // Find the used flags while traversing the tree
345     resetUsedFlags();
346
347     // Temporary list of links
348     QList<Link *> tmpLinks;
349
350     QString tree;
351     // Build xml recursivly
352     if (!saveSel) {
353         // Save all mapcenters as complete map, if saveSel not set
354         tree += saveTreeToDir(tmpdir, prefix, offset, tmpLinks);
355
356         // Save local settings
357         tree += settings.getDataXML(destPath);
358
359         // Save selection
360         if (getSelectedItem() && !saveSel)
361             tree += xml.valueElement("select", getSelectString());
362     }
363     else {
364         switch (saveSel->getType()) {
365         case TreeItem::Branch:
366             // Save Subtree
367             tree += ((BranchItem *)saveSel)
368                         ->saveToDir(tmpdir, prefix, offset, tmpLinks);
369             break;
370         case TreeItem::MapCenter:
371             // Save Subtree
372             tree += ((BranchItem *)saveSel)
373                         ->saveToDir(tmpdir, prefix, offset, tmpLinks);
374             break;
375         case TreeItem::Image:
376             // Save Image
377             tree += ((ImageItem *)saveSel)->saveToDir(tmpdir, prefix);
378             break;
379         default:
380             // other types shouldn't be safed directly...
381             break;
382         }
383     }
384
385     QString flags;
386
387     // Write images and definitions of used user flags
388     if (flagMode != FlagRowMaster::NoFlags) {
389         // First find out, which flags are used
390         // Definitions
391         flags += userFlagsMaster->saveDef(flagMode);
392
393         userFlagsMaster->saveDataToDir(tmpdir + "flags/user/", flagMode);
394         standardFlagsMaster->saveDataToDir(tmpdir + "flags/standard/",
395                                            flagMode);
396     }
397
398     QString footer;
399     // Save XLinks
400     for (int i = 0; i < tmpLinks.count(); ++i)
401         footer += tmpLinks.at(i)->saveToDir();
402
403     // Save slides
404     footer += slideModel->saveToDir();
405
406     xml.decIndent();
407     footer += xml.endElement("vymmap");
408
409     return header + flags + tree + footer;
410 }
411
412 QString VymModel::saveTreeToDir(const QString &tmpdir, const QString &prefix,
413                                 const QPointF &offset, QList<Link *> &tmpLinks)
414 {
415     QString s;
416     for (int i = 0; i < rootItem->branchCount(); i++)
417         s += rootItem->getBranchNum(i)->saveToDir(tmpdir, prefix, offset,
418                                                   tmpLinks);
419     return s;
420 }
421
422 void VymModel::setFilePath(QString fpath, QString destname)
423 {
424     if (fpath.isEmpty() || fpath == "") {
425         filePath = "";
426         fileName = "";
427         destPath = "";
428     }
429     else {
430         filePath = fpath;    // becomes absolute path
431         fileName = fpath;    // gets stripped of path
432         destPath = destname; // needed for vymlinks and during load to reset
433                              // fileChangedTime
434
435         // If fpath is not an absolute path, complete it
436         filePath = QDir(fpath).absolutePath();
437         fileDir = filePath.left(1 + filePath.lastIndexOf("/"));
438
439         // Set short name, too. Search from behind:
440         fileName = basename(fileName);
441
442         // Forget the .vym (or .xml) for name of map
443         mapName =
444             fileName.left(fileName.lastIndexOf(".", -1, Qt::CaseSensitive));
445     }
446 }
447
448 void VymModel::setFilePath(QString fpath) { setFilePath(fpath, fpath); }
449
450 QString VymModel::getFileDir() { return fileDir; }
451
452 QString VymModel::getFilePath() { return filePath; }
453
454 QString VymModel::getFileName() { return fileName; }
455
456 QString VymModel::getMapName() { return mapName; }
457
458 QString VymModel::getDestPath() { return destPath; }
459
460 bool VymModel::parseVymText(const QString &s)
461 {
462     bool ok = false;
463     BranchItem *bi = getSelectedBranch();
464     if (bi) {
465         parseBaseHandler *handler = new parseVYMHandler;
466
467         bool saveStateBlockedOrg = saveStateBlocked;
468         repositionBlocked = true;
469         saveStateBlocked = true;
470         QXmlInputSource source;
471         source.setData(s);
472         QXmlSimpleReader reader;
473         reader.setContentHandler(handler);
474         reader.setErrorHandler(handler);
475
476         handler->setInputString(s);
477         handler->setModel(this);
478         handler->setLoadMode(ImportReplace, 0);
479
480         ok = reader.parse(source);
481         repositionBlocked = false;
482         saveStateBlocked = saveStateBlockedOrg;
483         if (ok) {
484             if (s.startsWith("<vymnote"))
485                 emitNoteChanged(bi);
486             emitDataChanged(bi);
487             reposition(); // to generate bbox sizes
488         }
489         else {
490             QMessageBox::critical(0, tr("Critical Parse Error"),
491                                   tr(handler->errorProtocol().toUtf8()));
492             // returnCode=1;
493             // Still return "success": the map maybe at least
494             // partially read by the parser
495         }
496     }
497     return ok;
498 }
499
500 File::ErrorCode VymModel::loadMap(QString fname, const LoadMode &lmode,
501                                   const FileType &ftype,
502                                   const int &contentFilter, int pos)
503 {
504     File::ErrorCode err = File::Success;
505
506     // Get updated zoomFactor, before applying one read from file in the end
507     if (mapEditor) {
508         zoomFactor = mapEditor->getZoomFactorTarget();
509         rotationAngle = mapEditor->getAngleTarget();
510     }
511
512     parseBaseHandler *handler;
513     fileType = ftype;
514     switch (fileType) {
515     case VymMap:
516         handler = new parseVYMHandler;
517         ((parseVYMHandler *)handler)->setContentFilter(contentFilter);
518         break;
519     case FreemindMap:
520         handler = new parseFreemindHandler;
521         break;
522     default:
523         QMessageBox::critical(0, tr("Critical Parse Error"),
524                               "Unknown FileType in VymModel::load()");
525         return File::Aborted;
526     }
527
528     if (lmode == NewMap) {
529         // Reset timestamp to check for later updates of file
530         fileChangedTime = QFileInfo(destPath).lastModified();
531
532         selModel->clearSelection();
533     }
534
535     bool zipped_org = zipped;
536
537     // Create temporary directory for packing
538     bool ok;
539     QString tmpZipDir = makeTmpDir(ok, tmpDirPath(), "unzip");
540     if (!ok) {
541         QMessageBox::critical(
542             0, tr("Critical Load Error"),
543             tr("Couldn't create temporary directory before load\n"));
544         return File::Aborted;
545     }
546
547     QString xmlfile;
548     if (fname.right(4) == ".xml" || fname.right(3) == ".mm") {
549         xmlfile = fname;
550         zipped = false;
551
552         if (lmode == NewMap || lmode == DefaultMap)
553             zipped_org = false;
554     }
555     else {
556         // Try to unzip file
557         err = unzipDir(tmpZipDir, fname);
558     }
559
560     if (zipped) {
561         // Look for mapname.xml
562         xmlfile = fname.left(fname.lastIndexOf(".", -1, Qt::CaseSensitive));
563         xmlfile = xmlfile.section('/', -1);
564         QFile mfile(tmpZipDir + "/" + xmlfile + ".xml");
565         if (!mfile.exists()) {
566             // mapname.xml does not exist, well,
567             // maybe someone renamed the mapname.vym file...
568             // Try to find any .xml in the toplevel
569             // directory of the .vym file
570             QStringList filters;
571             filters << "*.xml";
572             QStringList flist = QDir(tmpZipDir).entryList(filters);
573             if (flist.count() == 1) {
574                 // Only one entry, take this one
575                 xmlfile = tmpZipDir + "/" + flist.first();
576             }
577             else {
578                 for (QStringList::Iterator it = flist.begin();
579                      it != flist.end(); ++it)
580                     *it = tmpZipDir + "/" + *it;
581                 // FIXME-4 Multiple entries, load all (but only the first one
582                 // into this ME)
583                 // mainWindow->fileLoadFromTmp (flist);
584                 // returnCode = 1;      // Silently forget this attempt to load
585                 qWarning("MainWindow::load (fn)  multimap found...");
586             }
587
588             if (flist.isEmpty()) {
589                 QMessageBox::critical(
590                     0, tr("Critical Load Error"),
591                     tr("Couldn't find a map (*.xml) in .vym archive.\n"));
592                 err = File::Aborted;
593             }
594         } // file doesn't exist
595         else
596             xmlfile = mfile.fileName();
597     }
598
599     QFile file(xmlfile);
600
601     // I am paranoid: file should exist anyway
602     // according to check in mainwindow.
603     if (!file.exists()) {
604         QMessageBox::critical(
605             0, tr("Critical Parse Error"),
606             tr(QString("Couldn't open map %1").arg(file.fileName()).toUtf8()));
607         err = File::Aborted;
608     }
609     else {
610         bool saveStateBlockedOrg = saveStateBlocked;
611         repositionBlocked = true;
612         saveStateBlocked = true;
613         mapEditor->setViewportUpdateMode(QGraphicsView::NoViewportUpdate);
614         QXmlInputSource source(&file);
615         QXmlSimpleReader reader;
616         reader.setContentHandler(handler);
617         reader.setErrorHandler(handler);
618         handler->setModel(this);
619
620         // We need to set the tmpDir in order  to load files with rel. path
621         QString tmpdir;
622         if (zipped)
623             tmpdir = tmpZipDir;
624         else
625             tmpdir = fname.left(fname.lastIndexOf("/", -1));
626         handler->setTmpDir(tmpdir);
627         handler->setInputFile(file.fileName());
628         if (lmode == ImportReplace)
629             handler->setLoadMode(ImportReplace, pos);
630         else
631             handler->setLoadMode(lmode, pos);
632
633         // Here we actually parse the XML file
634         bool ok = reader.parse(source);
635
636         // Aftermath
637         repositionBlocked = false;
638         saveStateBlocked = saveStateBlockedOrg;
639         mapEditor->setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate);
640         file.close();
641         if (ok) {
642             reposition(); // to generate bbox sizes
643             emitSelectionChanged();
644
645             if (lmode == NewMap) // no lockfile for default map!
646             {
647                 mapDefault = false;
648                 mapChanged = false;
649                 mapUnsaved = false;
650                 autosaveTimer->stop();
651
652                 resetHistory();
653                 resetSelectionHistory();
654
655                 // Set treeEditor and slideEditor visibilty per map
656                 vymView->readSettings();
657
658                 if (!tryVymLock() && debug)
659                     qWarning() << "VM::loadMap  no lockfile created!";
660             }
661
662             // Recalc priorities and sort
663             taskModel->recalcPriorities();
664         }
665         else {
666             QMessageBox::critical(0, tr("Critical Parse Error"),
667                                   tr(handler->errorProtocol().toUtf8()));
668             // returnCode=1;
669             // Still return "success": the map maybe at least
670             // partially read by the parser
671         }
672     }
673
674     // Delete tmpZipDir
675     removeDir(QDir(tmpZipDir));
676
677     // Restore original zip state
678     zipped = zipped_org;
679
680     updateActions();
681
682     if (lmode != NewMap)
683         emitUpdateQueries();
684
685     if (mapEditor) {
686         mapEditor->setZoomFactorTarget(zoomFactor);
687         mapEditor->setAngleTarget(rotationAngle);
688     }
689
690     qApp->processEvents(); // Update view (scene()->update() is not enough)
691     return err;
692 }
693
694 File::ErrorCode VymModel::save(const SaveMode &savemode)
695 {
696     QString tmpZipDir;
697     QString mapFileName;
698     QString saveFilePath;
699
700     File::ErrorCode err = File::Success;
701
702     if (zipped)
703         // save as .xml
704         mapFileName = mapName + ".xml";
705     else
706         // use name given by user, could be anything
707         mapFileName = fileName;
708
709     // Look, if we should zip the data:
710     if (!zipped)
711     {
712         QMessageBox mb(vymName,
713                        tr("The map %1\ndid not use the compressed "
714                           "vym file format.\nWriting it uncompressed will also "
715                           "write images \n"
716                           "and flags and thus may overwrite files into the "
717                           "given directory\n\nDo you want to write the map")
718                            .arg(filePath),
719                        QMessageBox::Warning,
720                        QMessageBox::Yes | QMessageBox::Default, QMessageBox::No,
721                        QMessageBox::Cancel | QMessageBox::Escape);
722         mb.setButtonText(QMessageBox::Yes, tr("compressed (vym default)"));
723         mb.setButtonText(
724             QMessageBox::No,
725             tr("uncompressed, potentially overwrite existing data"));
726         mb.setButtonText(QMessageBox::Cancel, tr("Cancel"));
727         switch (mb.exec()) {
728             case QMessageBox::Yes:
729                 // save compressed (default file format)
730                 zipped = true;
731                 break;
732             case QMessageBox::No:
733                 // save uncompressed
734                 zipped = false;
735                 break;
736             case QMessageBox::Cancel:
737                 // do nothing
738                 return File::Aborted;
739                 break;
740         }
741     }
742
743     // First backup existing file, we
744     // don't want to add to old zip archives
745     QFile f(destPath);
746     if (f.exists()) {
747         if (settings.value("/system/writeBackupFile").toBool()) {
748             QString backupFileName(destPath + "~");
749             QFile backupFile(backupFileName);
750             if (backupFile.exists() && !backupFile.remove()) {
751                 QMessageBox::warning(
752                     0, tr("Save Error"),
753                     tr("%1\ncould not be removed before saving")
754                         .arg(backupFileName));
755             }
756             else {
757                 if (!f.rename(backupFileName)) {
758                     QMessageBox::warning(
759                         0, tr("Save Error"),
760                         tr("%1\ncould not be renamed before saving")
761                             .arg(destPath));
762                 }
763             }
764         }
765     }
766
767     if (zipped) {
768         // Create temporary directory for packing
769         bool ok;
770         tmpZipDir = makeTmpDir(ok, tmpDirPath(), "zip");
771         if (!ok) {
772             QMessageBox::critical(
773                 0, tr("Critical Save Error"),
774                 tr("Couldn't create temporary directory before save\n"));
775             return File::Aborted;
776         }
777
778         saveFilePath = filePath;
779         setFilePath(tmpZipDir + "/" + mapName + ".xml", saveFilePath);
780     } // zipped
781
782     // Create mapName and fileDir
783     makeSubDirs(fileDir);
784
785     QString saveFile;
786     if (savemode == CompleteMap || selModel->selection().isEmpty()) {
787         // Save complete map
788         if (zipped)
789             // Use defined name for map within zipfile to avoid problems
790             // with zip library and umlauts (see #98)
791             saveFile =
792                 saveToDir(fileDir, "", FlagRowMaster::UsedFlags, QPointF(), NULL);
793         else
794             saveFile = saveToDir(fileDir, mapName + "-", FlagRowMaster::UsedFlags,
795                                  QPointF(), NULL);
796         mapChanged = false;
797         mapUnsaved = false;
798         autosaveTimer->stop();
799     }
800     else {
801         // Save part of map
802         if (selectionType() == TreeItem::Image)
803             saveImage();
804         else
805             saveFile = saveToDir(fileDir, mapName + "-", FlagRowMaster::UsedFlags,
806                                  QPointF(), getSelectedBranch());
807         // FIXME-3 take care of multiselections when saving parts
808     }
809
810     bool saved;
811     if (zipped)
812         // Use defined map name "map.xml", if zipped. Introduce in 2.6.6
813         saved = saveStringToDisk(fileDir + "map.xml", saveFile);
814     else
815         // Use regular mapName, when saved as XML
816         saved = saveStringToDisk(fileDir + mapFileName, saveFile);
817     if (!saved) {
818         err = File::Aborted;
819         qWarning("ME::saveStringToDisk failed!");
820     }
821
822     if (zipped) {
823         // zip
824         if (err == File::Success)
825             err = zipDir(tmpZipDir, destPath);
826
827         // Delete tmpDir
828         removeDir(QDir(tmpZipDir));
829
830         // Restore original filepath outside of tmp zip dir
831         setFilePath(saveFilePath);
832     }
833
834     updateActions();
835
836     fileChangedTime = QFileInfo(destPath).lastModified();
837     return err;
838 }
839
840 ImageItem* VymModel::loadImage(BranchItem *dst, const QString &fn)  // FIXME-2 better move filedialog to MainWindow
841 {
842     if (!dst)
843         dst = getSelectedBranch();
844     if (dst) {
845         QString filter = QString(tr("Images") +
846                                  " (*.png *.bmp *.xbm *.jpg *.png *.xpm *.gif "
847                                  "*.pnm *.svg *.svgz);;" +
848                                  tr("All", "Filedialog") + " (*.*)");
849         QStringList fns;
850         if (fn.isEmpty())
851             fns = QFileDialog::getOpenFileNames(
852                 NULL, vymName + " - " + tr("Load image"), lastImageDir.path(),
853                 filter);
854         else
855             fns.append(fn);
856
857         if (!fns.isEmpty()) {
858             lastImageDir.setPath(
859                 fns.first().left(fns.first().lastIndexOf("/")));
860             QString s;
861             for (int j = 0; j < fns.count(); j++) {
862                 s = fns.at(j);
863                 ImageItem *ii = createImage(dst);
864                 if (ii && ii->load(s)) {
865                     saveState((TreeItem *)ii, "remove()", dst,
866                               QString("loadImage (\"%1\")").arg(s),
867                               QString("Add image %1 to %2")
868                                   .arg(s)
869                                   .arg(getObjectName(dst)));
870                     // Find nice position for new image, take childPos // FIXME-1 position below last image
871                     FloatImageObj *fio = (FloatImageObj *)(ii->getMO());
872                     if (fio) {
873                         LinkableMapObj *parLMO = dst->getLMO();
874
875                         if (parLMO) {
876                             fio->move(parLMO->getChildRefPos());
877                             fio->setRelPos();
878                         }
879                     }
880
881                     // On default include image // FIXME-4 check, if we change
882                     // default settings...
883                     select(dst);
884                     setIncludeImagesHor(false);
885                     setIncludeImagesVer(true);
886
887                     reposition();
888                     return ii;
889                 }
890                 else {
891                     qWarning() << "vymmodel: Failed to load " + s;
892                     deleteItem(ii);
893                 }
894             }
895         }
896     }
897     return nullptr;
898 }
899
900 void VymModel::saveImage(ImageItem *ii, QString fn)
901 {
902     if (!ii)
903         ii = getSelectedImage();
904     if (ii) {
905         QString filter = QString(
906             tr("Images") +
907             " (*.png *.bmp *.xbm *.jpg *.png *.xpm *.gif *.pnm *.svg);;" +
908             tr("All", "Filedialog") + " (*.*)");
909         if (fn.isEmpty())
910             fn = QFileDialog::getSaveFileName(
911                 NULL, vymName + " - " + tr("Save image"), lastImageDir.path(),
912                 filter, NULL, QFileDialog::DontConfirmOverwrite);
913
914         if (!fn.isEmpty()) {
915             lastImageDir.setPath(fn.left(fn.lastIndexOf("/")));
916             if (QFile(fn).exists()) {
917                 QMessageBox mb(vymName,
918                                tr("The file %1 exists already.\n"
919                                   "Do you want to overwrite it?")
920                                    .arg(fn),
921                                QMessageBox::Warning,
922                                QMessageBox::Yes | QMessageBox::Default,
923                                QMessageBox::Cancel | QMessageBox::Escape,
924                                QMessageBox::NoButton);
925
926                 mb.setButtonText(QMessageBox::Yes, tr("Overwrite"));
927                 mb.setButtonText(QMessageBox::No, tr("Cancel"));
928                 switch (mb.exec()) {
929                 case QMessageBox::Yes:
930                     // save
931                     break;
932                 case QMessageBox::Cancel:
933                     // do nothing
934                     return;
935                     break;
936                 }
937             }
938             if (!ii->saveImage(fn))
939                 QMessageBox::critical(0, tr("Critical Error"),
940                                       tr("Couldn't save %1").arg(fn));
941         }
942     }
943 }
944
945 void VymModel::importDirInt(BranchItem *dst, QDir d)
946 {
947     bool oldSaveState = saveStateBlocked;
948     saveStateBlocked = true;
949     BranchItem *bi = dst;
950     if (bi) {
951         int beginDepth = bi->depth();
952
953         d.setFilter(QDir::AllEntries | QDir::Hidden);
954         QFileInfoList list = d.entryInfoList();
955         QFileInfo fi;
956
957         // Traverse directories
958         for (int i = 0; i < list.size(); ++i) {
959             fi = list.at(i);
960             if (fi.isDir() && fi.fileName() != "." && fi.fileName() != "..") {
961                 bi = addNewBranchInt(dst, -2);
962                 bi->setHeadingPlainText(fi.fileName());
963                 bi->setHeadingColor(QColor("blue"));
964                 if (debug)
965                     qDebug() << "Added subdir: " << fi.fileName();
966                 if (!d.cd(fi.fileName()))
967                     QMessageBox::critical(
968                         0, tr("Critical Import Error"),
969                         tr("Cannot find the directory %1").arg(fi.fileName()));
970                 else {
971                     // Recursively add subdirs
972                     importDirInt(bi, d);
973                     d.cdUp();
974                 }
975                 emitDataChanged(bi);
976             }
977         }
978
979         for (int i = 0; i < list.size(); ++i) {
980             fi = list.at(i);
981             if (fi.isFile()) {
982                 bi = addNewBranchInt(dst, -2);
983                 bi->setHeadingPlainText(fi.fileName());
984                 bi->setHeadingColor(QColor("black"));
985                 if (fi.fileName().right(4) == ".vym")
986                     bi->setVymLink(fi.filePath());
987                 emitDataChanged(bi);
988             }
989         }
990
991         // Scroll at least some stuff
992         if (dst->branchCount() > 1 && dst->depth() - beginDepth > 2)
993             dst->toggleScroll();
994     }
995     saveStateBlocked = oldSaveState;
996 }
997
998 void VymModel::importDir(const QString &s)
999 {
1000     BranchItem *selbi = getSelectedBranch();
1001     if (selbi) {
1002         saveStateChangingPart(
1003             selbi, selbi, QString("importDir (\"%1\")").arg(s),
1004             QString("Import directory structure from %1").arg(s));
1005
1006         QDir d(s);
1007         importDirInt(selbi, d);
1008     }
1009 }
1010
1011 void VymModel::importDir()
1012 {
1013     BranchItem *selbi = getSelectedBranch();
1014     if (selbi) {
1015         QStringList filters;
1016         filters << "VYM map (*.vym)";
1017         QFileDialog fd;
1018         fd.setWindowTitle(vymName + " - " +
1019                           tr("Choose directory structure to import"));
1020         fd.setFileMode(QFileDialog::DirectoryOnly);
1021         fd.setNameFilters(filters);
1022         fd.setWindowTitle(vymName + " - " +
1023                           tr("Choose directory structure to import"));
1024         fd.setAcceptMode(QFileDialog::AcceptOpen);
1025
1026         QString fn;
1027         if (fd.exec() == QDialog::Accepted && !fd.selectedFiles().isEmpty()) {
1028             importDir(fd.selectedFiles().first());
1029             reposition();
1030         }
1031     }
1032 }
1033
1034 bool VymModel::removeVymLock()
1035 {
1036     if (vymLock.removeLockForced()) {
1037         mainWindow->statusMessage(tr("Removed lockfile for %1").arg(mapName));
1038         setReadOnly(false);
1039         return true;
1040     } else
1041         return false;
1042 }
1043
1044 bool VymModel::tryVymLock()
1045 {
1046     // Defaults for author and host in vymLock
1047     QString defAuthor =
1048         settings
1049             .value("/user/name",
1050                    tr("unknown user", "Default for lockfiles of maps"))
1051             .toString();
1052     QString defHost = QHostInfo::localHostName();
1053     vymLock.setMapPath(filePath);
1054     vymLock.setAuthor(settings.value("/user/name", defAuthor).toString());
1055     if (getenv("HOST") != 0)
1056         vymLock.setHost(getenv("HOST"));
1057     else
1058         vymLock.setHost(defHost);
1059
1060     // Now try to lock
1061     if (!vymLock.tryLock()) {
1062         if (debug)
1063             qDebug() << "VymModel::tryLock failed!";
1064         setReadOnly(true);
1065         if (vymLock.getState() == VymLock::LockedByOther) {
1066             if (restoreMode) {
1067                 // While restoring maps, existing lockfiles will be ignored for
1068                 // loading, but listed in a warning dialog
1069                 ignoredLockedFiles << filePath;
1070                 return removeVymLock();
1071             }
1072             else {
1073                 LockedFileDialog dia;
1074                 QString a = vymLock.getAuthor();
1075                 QString h = vymLock.getHost();
1076                 QString s =
1077                     QString(
1078                         tr("Map seems to be already opened in another vym "
1079                            "instance!\n\n "
1080                            "Map is locked by \"%1\" on \"%2\"\n\n"
1081                            "Please only delete the lockfile, if you are sure "
1082                            "nobody else is currently working on this map."))
1083                         .arg(a)
1084                         .arg(h);
1085                 dia.setText(s);
1086                 dia.setWindowTitle(
1087                     tr("Warning: Map already opended", "VymModel"));
1088                 if (dia.execDialog() == LockedFileDialog::DeleteLockfile) {
1089                     if (!removeVymLock()) {
1090                         // Could not remove existing lockfile, give up
1091                         QMessageBox::warning(
1092                             0, tr("Warning"),
1093                             tr("Couldn't remove lockfile for %1").arg(mapName));
1094                         return false;
1095                     }
1096                     if (!tryVymLock()) {
1097                         // Was able to remove existing lockfile, but not able to 
1098                         // create new one.
1099                         qWarning() << "VymModel::tryVymLock could not create new lockfile after removing old";
1100                         return false;
1101                     }
1102                 }
1103             }
1104         }
1105         else if (vymLock.getState() == VymLock::NotWritable) {
1106             WarningDialog dia;
1107             QString s = QString(tr("Cannot create lockfile of map! "
1108                                    "It will be opened in readonly mode.\n\n"));
1109             dia.setText(s);
1110             dia.setWindowTitle(tr("Warning", "VymModel"));
1111             dia.showCancelButton(false);
1112             // dia.setShowAgainName("/mainwindow/mapIsLocked");
1113             dia.exec();
1114         }
1115         return false;
1116     }
1117     return true;
1118 }
1119
1120 bool VymModel::renameMap(const QString &newPath)
1121 {
1122     QString oldPath = filePath;
1123     if (vymLock.getState() == VymLock::LockedByMyself || vymLock.getState() == VymLock::Undefined) {
1124         // vymModel owns the lockfile, try to create new lock
1125         VymLock newLock;
1126         newLock = vymLock;
1127         newLock.setMapPath(newPath);    // Resets state for newLock to "Undefined"
1128         if (!newLock.tryLock()) {
1129             qWarning() << QString("VymModel::renameMap  could not create lockfile for %1").arg(newPath);
1130             return false;
1131         }
1132
1133         // Change lockfiles now
1134         if (!vymLock.releaseLock())
1135             qWarning() << "VymModel::renameMap failed to release lock for " << oldPath;
1136         vymLock = newLock;
1137         setFilePath(newPath);
1138         if (readonly)
1139             setReadOnly(false);
1140         return true;
1141     }
1142     qWarning() << "VymModel::renameMap failed to get lockfile. state=" << vymLock.getState();
1143     return false;
1144 }
1145
1146 void VymModel::setReadOnly(bool b)
1147 {
1148     readonly = b;
1149     mainWindow->updateTabName(this);
1150 }
1151
1152 bool VymModel::isReadOnly() { return readonly; }
1153
1154 void VymModel::autosave()
1155 {
1156     // Check if autosave is disabled due to testmode
1157     if (testmode)
1158     {
1159         qWarning()
1160             << QString("VymModel::autosave disabled in testmode!  Current map: %1")
1161                    .arg(filePath);
1162         return;
1163     }
1164
1165     // Check if autosave is disabled globally
1166     if (!mainWindow->useAutosave()) {
1167         qWarning()
1168             << QString("VymModel::autosave disabled globally!  Current map: %1")
1169                    .arg(filePath);
1170         return;
1171     }
1172
1173     QDateTime now = QDateTime().currentDateTime();
1174
1175     // Disable autosave, while we have gone back in history
1176     int redosAvail = undoSet.numValue(QString("/history/redosAvail"));
1177     if (redosAvail > 0)
1178         return;
1179
1180     // Also disable autosave for new map without filename
1181     if (filePath.isEmpty()) {
1182         if (debug)
1183             qWarning()
1184                 << "VymModel::autosave rejected due to missing filePath\n";
1185         return;
1186     }
1187
1188     if (mapUnsaved && mapChanged && mainWindow->useAutosave() && !testmode) {
1189         if (QFileInfo(filePath).lastModified() <= fileChangedTime)
1190             mainWindow->fileSave(this);
1191         else if (debug)
1192             qDebug() << "  ME::autosave  rejected, file on disk is newer than "
1193                         "last save.\n";
1194     }
1195 }
1196
1197 void VymModel::fileChanged()
1198 {
1199     // Check if file on disk has changed meanwhile
1200     if (!filePath.isEmpty()) {
1201         if (readonly && vymLock.getState() != VymLock::LockedByMyself) {
1202             // unset readonly if lockfile is gone
1203             // but only, if map was LockedByOther before
1204             if (vymLock.tryLock())
1205                 setReadOnly(false);
1206         }
1207         else {
1208             // FIXME-5 We could check, if somebody else removed/replaced lockfile
1209             // (A unique vym ID would be needed)
1210
1211             QDateTime tmod = QFileInfo(filePath).lastModified();
1212             if (tmod > fileChangedTime) {
1213                 // FIXME-4 VM switch to current mapeditor and finish
1214                 // lineedits...
1215                 QMessageBox mb(
1216                     vymName,
1217                     tr("The file of the map  on disk has changed:\n\n"
1218                        "   %1\n\nDo you want to reload that map with the new "
1219                        "file?")
1220                         .arg(filePath),
1221                     QMessageBox::Question, QMessageBox::Yes,
1222                     QMessageBox::Cancel | QMessageBox::Default,
1223                     QMessageBox::NoButton);
1224
1225                 mb.setButtonText(QMessageBox::Yes, tr("Reload"));
1226                 mb.setButtonText(QMessageBox::No, tr("Ignore"));
1227                 switch (mb.exec()) {
1228                 case QMessageBox::Yes:
1229                     // Reload map
1230                     mainWindow->initProgressCounter(1);
1231                     loadMap(filePath);
1232                     mainWindow->removeProgressCounter();
1233                     break;
1234                 case QMessageBox::Cancel:
1235                     fileChangedTime =
1236                         tmod; // allow autosave to overwrite newer file!
1237                 }
1238             }
1239         }
1240     }
1241 }
1242
1243 void VymModel::blockReposition()
1244 {
1245     repositionBlocked = true;
1246 }
1247
1248 void VymModel::unblockReposition()
1249 {
1250     repositionBlocked = false;
1251     reposition();
1252 }
1253
1254 bool VymModel::isDefault() { return mapDefault; }
1255
1256 void VymModel::makeDefault()
1257 {
1258     mapChanged = false;
1259     mapDefault = true;
1260 }
1261
1262 bool VymModel::hasChanged() { return mapChanged; }
1263
1264 void VymModel::setChanged()
1265 {
1266     if (!mapChanged)
1267         autosaveTimer->start(
1268             settings.value("/system/autosave/ms/", 30000).toInt());
1269     mapChanged = true;
1270     mapDefault = false;
1271     mapUnsaved = true;
1272     updateActions();
1273 }
1274
1275 QString VymModel::getObjectName(LinkableMapObj *lmo)
1276 {
1277     if (!lmo || !lmo->getTreeItem())
1278         return QString();
1279     return getObjectName(lmo->getTreeItem());
1280 }
1281
1282 QString VymModel::getObjectName(TreeItem *ti)
1283 {
1284     QString s;
1285     if (!ti)
1286         return QString("Error: NULL has no name!");
1287     s = ti->getHeadingPlain();
1288     if (s == "")
1289         s = "unnamed";
1290
1291     return QString("%1 (%2)").arg(ti->getTypeName()).arg(s);
1292 }
1293
1294 void VymModel::redo()
1295 {
1296     // Can we undo at all?
1297     if (redosAvail < 1)
1298         return;
1299
1300     bool saveStateBlockedOrg = saveStateBlocked;
1301     saveStateBlocked = true;
1302
1303     redosAvail--;
1304
1305     if (undosAvail < stepsTotal)
1306         undosAvail++;
1307     curStep++;
1308     if (curStep > stepsTotal)
1309         curStep = 1;
1310     QString undoCommand =
1311         undoSet.value(QString("/history/step-%1/undoCommand").arg(curStep));
1312     QString undoSelection =
1313         undoSet.value(QString("/history/step-%1/undoSelection").arg(curStep));
1314     QString redoCommand =
1315         undoSet.value(QString("/history/step-%1/redoCommand").arg(curStep));
1316     QString redoSelection =
1317         undoSet.value(QString("/history/step-%1/redoSelection").arg(curStep));
1318     QString comment =
1319         undoSet.value(QString("/history/step-%1/comment").arg(curStep));
1320     QString version = undoSet.value("/history/version");
1321
1322     /* TODO Maybe check for version, if we save the history
1323     if (!checkVersion(version))
1324     QMessageBox::warning(0,tr("Warning"),
1325         tr("Version %1 of saved undo/redo data\ndoes not match current vym
1326     version %2.").arg(version).arg(vymVersion));
1327     */
1328
1329     // Find out current undo directory
1330     QString bakMapDir(QString(tmpMapDirPath + "/undo-%1").arg(curStep));
1331
1332     if (debug) {
1333         qDebug() << "VymModel::redo() begin\n";
1334         qDebug() << "    undosAvail=" << undosAvail;
1335         qDebug() << "    redosAvail=" << redosAvail;
1336         qDebug() << "       curStep=" << curStep;
1337         qDebug() << "    ---------------------------";
1338         qDebug() << "    comment=" << comment;
1339         qDebug() << "    undoSel=" << undoSelection;
1340         qDebug() << "    redoSel=" << redoSelection;
1341         qDebug() << "    undoCom:";
1342         cout << qPrintable(undoCommand);
1343         qDebug() << "    redoCom=";
1344         cout << qPrintable(redoCommand);
1345         qDebug() << "    ---------------------------";
1346     }
1347
1348     // select  object before redo
1349     if (!redoSelection.isEmpty())
1350         select(redoSelection);
1351
1352     QString errMsg;
1353     QString redoScript =
1354         QString("model = vym.currentMap(); model.%1").arg(redoCommand);
1355     errMsg = QVariant(execute(redoScript)).toString();
1356     saveStateBlocked = saveStateBlockedOrg;
1357
1358     undoSet.setValue("/history/undosAvail", QString::number(undosAvail));
1359     undoSet.setValue("/history/redosAvail", QString::number(redosAvail));
1360     undoSet.setValue("/history/curStep", QString::number(curStep));
1361     undoSet.writeSettings(histPath);
1362
1363     mainWindow->updateHistory(undoSet);
1364     updateActions();
1365
1366     /* TODO remove testing
1367     qDebug() << "ME::redo() end\n";
1368     qDebug() << "    undosAvail="<<undosAvail;
1369     qDebug() << "    redosAvail="<<redosAvail;
1370     qDebug() << "       curStep="<<curStep;
1371     qDebug() << "    ---------------------------";
1372     */
1373 }
1374
1375 bool VymModel::isRedoAvailable()
1376 {
1377     if (undoSet.numValue("/history/redosAvail", 0) > 0)
1378         return true;
1379     else
1380         return false;
1381 }
1382
1383 QString VymModel::lastRedoSelection()
1384 {
1385     if (isUndoAvailable())
1386         return undoSet.value(
1387             QString("/history/step-%1/redoSelection").arg(curStep));
1388     else
1389         return QString();
1390 }
1391
1392 QString VymModel::lastRedoCommand()
1393 {
1394     if (isUndoAvailable())
1395         return undoSet.value(
1396             QString("/history/step-%1/redoCommand").arg(curStep));
1397     else
1398         return QString();
1399 }
1400
1401 QVariant VymModel::repeatLastCommand()
1402 {
1403     QString command = "m = vym.currentMap();";
1404     if (isUndoAvailable())
1405         command += "m." +
1406                    undoSet.value(
1407                        QString("/history/step-%1/redoCommand").arg(curStep)) +
1408                    ";";
1409     else
1410         return false;
1411     return execute(command);
1412 }
1413
1414 void VymModel::undo()
1415 {
1416     // Can we undo at all?
1417     if (undosAvail < 1)
1418         return;
1419
1420     mainWindow->statusMessage(tr("Autosave disabled during undo."));
1421
1422     bool saveStateBlockedOrg = saveStateBlocked;
1423     saveStateBlocked = true;
1424
1425     QString undoCommand =
1426         undoSet.value(QString("/history/step-%1/undoCommand").arg(curStep));
1427     QString undoSelection =
1428         undoSet.value(QString("/history/step-%1/undoSelection").arg(curStep));
1429     QString redoCommand =
1430         undoSet.value(QString("/history/step-%1/redoCommand").arg(curStep));
1431     QString redoSelection =
1432         undoSet.value(QString("/history/step-%1/redoSelection").arg(curStep));
1433     QString comment =
1434         undoSet.value(QString("/history/step-%1/comment").arg(curStep));
1435     QString version = undoSet.value("/history/version");
1436
1437     /* TODO Maybe check for version, if we save the history
1438     if (!checkVersion(version))
1439     QMessageBox::warning(0,tr("Warning"),
1440         tr("Version %1 of saved undo/redo data\ndoes not match current vym
1441     version %2.").arg(version).arg(vymVersion));
1442     */
1443
1444     // Find out current undo directory
1445     QString bakMapDir(QString(tmpMapDirPath + "/undo-%1").arg(curStep));
1446
1447     // select  object before undo
1448     if (!undoSelection.isEmpty() && !select(undoSelection)) {
1449         qWarning("VymModel::undo()  Could not select object for undo");
1450         return;
1451     }
1452
1453     if (debug) {
1454         qDebug() << "VymModel::undo() begin\n";
1455         qDebug() << "    undosAvail=" << undosAvail;
1456         qDebug() << "    redosAvail=" << redosAvail;
1457         qDebug() << "       curStep=" << curStep;
1458         cout << "    ---------------------------" << endl;
1459         qDebug() << "    comment=" << comment;
1460         qDebug() << "    undoSel=" << undoSelection;
1461         qDebug() << "    redoSel=" << redoSelection;
1462         cout << "    undoCom:" << endl;
1463         cout << qPrintable(undoCommand) << endl;
1464         cout << "    redoCom:" << endl;
1465         cout << qPrintable(redoCommand) << endl;
1466         cout << "    ---------------------------" << endl;
1467     }
1468
1469     // select  object before undo   // FIXME-2 double select, see above
1470     if (!undoSelection.isEmpty())
1471         select(undoSelection);
1472
1473     // bool noErr;
1474     QString errMsg;
1475     QString undoScript =
1476         QString("model = vym.currentMap(); model.%1").arg(undoCommand);
1477     errMsg = QVariant(execute(undoScript)).toString();
1478
1479     undosAvail--;
1480     curStep--;
1481     if (curStep < 1)
1482         curStep = stepsTotal;
1483
1484     redosAvail++;
1485
1486     saveStateBlocked = saveStateBlockedOrg;
1487     /* testing only
1488         qDebug() << "VymModel::undo() end\n";
1489         qDebug() << "    undosAvail="<<undosAvail;
1490         qDebug() << "    redosAvail="<<redosAvail;
1491         qDebug() << "       curStep="<<curStep;
1492         qDebug() << "    ---------------------------";
1493     */
1494
1495     undoSet.setValue("/history/undosAvail", QString::number(undosAvail));
1496     undoSet.setValue("/history/redosAvail", QString::number(redosAvail));
1497     undoSet.setValue("/history/curStep", QString::number(curStep));
1498     undoSet.writeSettings(histPath);
1499
1500     mainWindow->updateHistory(undoSet);
1501     updateActions();
1502 }
1503
1504 bool VymModel::isUndoAvailable()
1505 {
1506     if (undoSet.numValue("/history/undosAvail", 0) > 0)
1507         return true;
1508     return false;
1509 }
1510
1511 void VymModel::gotoHistoryStep(int i)
1512 {
1513     // Restore variables
1514     int undosAvail = undoSet.numValue(QString("/history/undosAvail"));
1515     int redosAvail = undoSet.numValue(QString("/history/redosAvail"));
1516
1517     if (i < 0)
1518         i = undosAvail + redosAvail;
1519
1520     // Clicking above current step makes us undo things
1521     if (i < undosAvail) {
1522         for (int j = 0; j < undosAvail - i; j++)
1523             undo();
1524         return;
1525     }
1526     // Clicking below current step makes us redo things
1527     if (i > undosAvail)
1528         for (int j = undosAvail; j < i; j++) {
1529             if (debug)
1530                 qDebug() << "VymModel::gotoHistoryStep redo " << j << "/"
1531                          << undosAvail << " i=" << i;
1532             redo();
1533         }
1534
1535     // And ignore clicking the current row ;-)
1536 }
1537
1538 QString VymModel::getHistoryPath()
1539 {
1540     QString histName(QString("history-%1").arg(curStep));
1541     return (tmpMapDirPath + "/" + histName);
1542 }
1543
1544 void VymModel::resetHistory()
1545 {
1546     curStep = 0;
1547     redosAvail = 0;
1548     undosAvail = 0;
1549
1550     stepsTotal = settings.value("/history/stepsTotal", 100).toInt();
1551     undoSet.setValue("/history/stepsTotal", QString::number(stepsTotal));
1552     mainWindow->updateHistory(undoSet);
1553 }
1554
1555 void VymModel::saveState(const SaveMode &savemode, const QString &undoSelection,
1556                          const QString &undoCom, const QString &redoSelection,
1557                          const QString &redoCom, const QString &comment,
1558                          TreeItem *saveSel, QString dataXML)
1559 {
1560     sendData(redoCom); // FIXME-4 testing
1561
1562     // Main saveState
1563
1564     if (saveStateBlocked)
1565         return;
1566
1567     if (debug)
1568         qDebug() << "VM::saveState() for map " << mapName;
1569
1570     QString undoCommand = undoCom;
1571     QString redoCommand = redoCom;
1572
1573
1574     // Increase undo steps, but check for repeated actions
1575     // like editing a vymNote - then do not increase but replace last command
1576     /*
1577     QRegExp re ("parseVymText.*\\(.*vymnote");
1578     if (curStep > 0 && redoSelection == lastRedoSelection() &&
1579         lastRedoCommand().contains(re)) {
1580         undoCommand = undoSet.value(
1581             QString("/history/step-%1/undoCommand").arg(curStep), undoCommand);
1582     }
1583     else {
1584     */
1585         if (undosAvail < stepsTotal)
1586             undosAvail++;
1587
1588         curStep++;
1589         if (curStep > stepsTotal)
1590             curStep = 1;
1591     //}
1592
1593     QString histDir = getHistoryPath();
1594     QString bakMapPath = histDir + "/map.xml";
1595
1596     // Create histDir if not available
1597     QDir d(histDir);
1598     if (!d.exists())
1599         makeSubDirs(histDir);
1600
1601     // Save depending on how much needs to be saved
1602     QList<Link *> tmpLinks;
1603     if (saveSel)
1604         dataXML = saveToDir(histDir, mapName + "-", FlagRowMaster::NoFlags, QPointF(),
1605                             saveSel);
1606
1607     if (savemode == PartOfMap) {
1608         undoCommand.replace("PATH", bakMapPath);
1609         redoCommand.replace("PATH", bakMapPath);
1610     }
1611
1612     if (!dataXML.isEmpty())
1613         // Write XML Data to disk
1614         saveStringToDisk(bakMapPath, dataXML);
1615
1616     // We would have to save all actions in a tree, to keep track of
1617     // possible redos after a action. Possible, but we are too lazy: forget
1618     // about redos.
1619     redosAvail = 0;
1620
1621     // Write the current state to disk
1622     undoSet.setValue("/history/undosAvail", QString::number(undosAvail));
1623     undoSet.setValue("/history/redosAvail", QString::number(redosAvail));
1624     undoSet.setValue("/history/curStep", QString::number(curStep));
1625     undoSet.setValue(QString("/history/step-%1/undoCommand").arg(curStep),
1626                      undoCommand);
1627     undoSet.setValue(QString("/history/step-%1/undoSelection").arg(curStep),
1628                      undoSelection);
1629     undoSet.setValue(QString("/history/step-%1/redoCommand").arg(curStep),
1630                      redoCommand);
1631     undoSet.setValue(QString("/history/step-%1/redoSelection").arg(curStep),
1632                      redoSelection);
1633     undoSet.setValue(QString("/history/step-%1/comment").arg(curStep), comment);
1634     undoSet.setValue(QString("/history/version"), vymVersion);
1635     undoSet.writeSettings(histPath);
1636
1637     if (debug) {
1638         // qDebug() << "          into="<< histPath;
1639         qDebug() << "    stepsTotal=" << stepsTotal
1640                  << ", undosAvail=" << undosAvail
1641                  << ", redosAvail=" << redosAvail << ", curStep=" << curStep;
1642         cout << "    ---------------------------" << endl;
1643         qDebug() << "    comment=" << comment;
1644         qDebug() << "    undoSel=" << undoSelection;
1645         qDebug() << "    redoSel=" << redoSelection;
1646         if (saveSel)
1647             qDebug() << "    saveSel=" << qPrintable(getSelectString(saveSel));
1648         cout << "    undoCom:" << endl;
1649         cout << qPrintable(undoCommand) << endl;
1650         cout << "    redoCom:" << endl;
1651         cout << qPrintable(redoCommand) << endl;
1652         cout << "    ---------------------------" << endl;
1653     }
1654
1655     mainWindow->updateHistory(undoSet);
1656
1657     setChanged();
1658 }
1659
1660 void VymModel::saveStateChangingPart(TreeItem *undoSel, TreeItem *redoSel,
1661                                      const QString &rc, const QString &comment)
1662 {
1663     // save the selected part of the map, Undo will replace part of map
1664     QString undoSelection = "";
1665     if (undoSel)
1666         undoSelection = getSelectString(undoSel);
1667     else
1668         qWarning("VymModel::saveStateChangingPart  no undoSel given!");
1669     QString redoSelection = "";
1670     if (redoSel)
1671         redoSelection = getSelectString(undoSel);
1672     else
1673         qWarning("VymModel::saveStateChangingPart  no redoSel given!");
1674
1675     saveState(PartOfMap, undoSelection, "addMapReplace (\"PATH\")",
1676               redoSelection, rc, comment, undoSel);
1677 }
1678
1679 void VymModel::saveStateRemovingPart(TreeItem *redoSel, const QString &comment)
1680 {
1681     if (!redoSel) {
1682         qWarning("VymModel::saveStateRemovingPart  no redoSel given!");
1683         return;
1684     }
1685     QString undoSelection;
1686     QString redoSelection = getSelectString(redoSel);
1687     if (redoSel->isBranchLikeType()) {
1688         // save the selected branch of the map, Undo will insert part of map
1689         if (redoSel->depth() > 0)
1690             undoSelection = getSelectString(redoSel->parent());
1691         saveState(PartOfMap, undoSelection,
1692                   QString("addMapInsert (\"PATH\",%1,%2)")
1693                       .arg(redoSel->num())
1694                       .arg(SlideContent),
1695                   redoSelection, "remove ()", comment, redoSel);
1696     }
1697 }
1698
1699 void VymModel::saveState(TreeItem *undoSel, const QString &uc,
1700                          TreeItem *redoSel, const QString &rc,
1701                          const QString &comment)
1702 {
1703     // "Normal" savestate: save commands, selections and comment
1704     // so just save commands for undo and redo
1705     // and use current selection, if empty parameter passed
1706
1707     QString redoSelection = "";
1708     if (redoSel)
1709         redoSelection = getSelectString(redoSel);
1710     QString undoSelection = "";
1711     if (undoSel)
1712         undoSelection = getSelectString(undoSel);
1713
1714     saveState(UndoCommand, undoSelection, uc, redoSelection, rc, comment, NULL);
1715 }
1716
1717 void VymModel::saveState(const QString &undoSel, const QString &uc,
1718                          const QString &redoSel, const QString &rc,
1719                          const QString &comment)
1720 {
1721     // "Normal" savestate: save commands, selections and comment
1722     // so just save commands for undo and redo
1723     // and use current selection
1724     saveState(UndoCommand, undoSel, uc, redoSel, rc, comment, NULL);
1725 }
1726
1727 void VymModel::saveState(const QString &uc, const QString &rc,
1728                          const QString &comment)
1729 {
1730     // "Normal" savestate applied to model (no selection needed):
1731     // save commands  and comment
1732     saveState(UndoCommand, NULL, uc, NULL, rc, comment, NULL);
1733 }
1734
1735 void VymModel::saveStateMinimal(TreeItem *undoSel, const QString &uc,
1736                                 TreeItem *redoSel, const QString &rc,
1737                                 const QString &comment)
1738 { //  Save a change in string and merge
1739     //  minor sequential  changes  */
1740     QString redoSelection = "";
1741     if (redoSel)
1742         redoSelection = getSelectString(redoSel);
1743     QString undoSelection = "";
1744     if (undoSel)
1745         undoSelection = getSelectString(undoSel);
1746
1747     saveState(UndoCommand, undoSelection, uc, redoSelection, rc, comment, NULL);
1748 }
1749
1750 void VymModel::saveStateBeforeLoad(LoadMode lmode, const QString &fname)
1751 {
1752     BranchItem *selbi = getSelectedBranch();
1753     if (selbi) {
1754         if (lmode == ImportAdd)
1755             saveStateChangingPart(selbi, selbi,
1756                                   QString("addMapInsert (\"%1\")").arg(fname),
1757                                   QString("Add map %1 to %2")
1758                                       .arg(fname)
1759                                       .arg(getObjectName(selbi)));
1760         if (lmode == ImportReplace) {
1761             BranchItem *pi = (BranchItem *)(selbi->parent());
1762             saveStateChangingPart(pi, pi,
1763                                   QString("addMapReplace(%1)").arg(fname),
1764                                   QString("Add map %1 to %2")
1765                                       .arg(fname)
1766                                       .arg(getObjectName(selbi)));
1767         }
1768     }
1769 }
1770
1771 QGraphicsScene *VymModel::getScene() { return mapEditor->getScene(); }
1772
1773 TreeItem *VymModel::findBySelectString(QString s)
1774 {
1775     if (s.isEmpty())
1776         return NULL;
1777
1778     // Old maps don't have multiple mapcenters and don't save full path
1779     if (s.left(2) != "mc")
1780         s = "mc:0," + s;
1781
1782     QStringList parts = s.split(",");
1783     QString typ;
1784     int n;
1785     TreeItem *ti = rootItem;
1786
1787     while (!parts.isEmpty()) {
1788         typ = parts.first().left(2);
1789         n = parts.first().right(parts.first().length() - 3).toInt();
1790         parts.removeFirst();
1791         if (typ == "mc" || typ == "bo")
1792             ti = ti->getBranchNum(n);
1793         else if (typ == "fi")
1794             ti = ti->getImageNum(n);
1795         else if (typ == "ai")
1796             ti = ti->getAttributeNum(n);
1797         else if (typ == "xl")
1798             ti = ti->getXLinkItemNum(n);
1799         if (!ti)
1800             return NULL;
1801     }
1802     return ti;
1803 }
1804
1805 TreeItem *VymModel::findID(const uint &id)
1806 {
1807     BranchItem *cur = NULL;
1808     BranchItem *prev = NULL;
1809     nextBranch(cur, prev);
1810     while (cur) {
1811         if (id == cur->getID())
1812             return cur;
1813         int j = 0;
1814         while (j < cur->xlinkCount()) {
1815             XLinkItem *xli = cur->getXLinkItemNum(j);
1816             if (id == xli->getID())
1817                 return xli;
1818             j++;
1819         }
1820         j = 0;
1821         while (j < cur->imageCount()) {
1822             ImageItem *ii = cur->getImageNum(j);
1823             if (id == ii->getID())
1824                 return ii;
1825             j++;
1826         }
1827         nextBranch(cur, prev);
1828     }
1829     return NULL;
1830 }
1831
1832 TreeItem *VymModel::findUuid(const QUuid &id)
1833 {
1834     BranchItem *cur = NULL;
1835     BranchItem *prev = NULL;
1836     nextBranch(cur, prev);
1837     while (cur) {
1838         if (id == cur->getUuid())
1839             return cur;
1840         int j = 0;
1841         while (j < cur->xlinkCount()) {
1842             XLinkItem *xli = cur->getXLinkItemNum(j);
1843             if (id == xli->getUuid())
1844                 return xli;
1845             j++;
1846         }
1847         j = 0;
1848         while (j < cur->imageCount()) {
1849             ImageItem *ii = cur->getImageNum(j);
1850             if (id == ii->getUuid())
1851                 return ii;
1852             j++;
1853         }
1854         nextBranch(cur, prev);
1855     }
1856     return NULL;
1857 }
1858
1859 //////////////////////////////////////////////
1860 // Interface
1861 //////////////////////////////////////////////
1862 void VymModel::setVersion(const QString &s) { version = s; }
1863
1864 QString VymModel::getVersion() { return version; }
1865
1866 void VymModel::setTitle(const QString &s)
1867 {
1868     saveState(QString("setMapTitle (\"%1\")").arg(title),
1869               QString("setMapTitle (\"%1\")").arg(s),
1870               QString("Set title of map to \"%1\"").arg(s));
1871     title = s;
1872 }
1873
1874 QString VymModel::getTitle() { return title; }
1875
1876 void VymModel::setAuthor(const QString &s)
1877 {
1878     saveState(QString("setMapAuthor (\"%1\")").arg(author),
1879               QString("setMapAuthor (\"%1\")").arg(s),
1880               QString("Set author of map to \"%1\"").arg(s));
1881     author = s;
1882 }
1883
1884 QString VymModel::getAuthor() { return author; }
1885
1886 void VymModel::setComment(const QString &s)
1887 {
1888     saveState(QString("setMapComment (\"%1\")").arg(comment),
1889               QString("setMapComment (\"%1\")").arg(s),
1890               QString("Set comment of map"));
1891     comment = s;
1892 }
1893
1894 QString VymModel::getComment() { return comment; }
1895
1896 QString VymModel::getDate()
1897 {
1898     return QDate::currentDate().toString("yyyy-MM-dd");
1899 }
1900
1901 int VymModel::branchCount()
1902 {
1903     int c = 0;
1904     BranchItem *cur = NULL;
1905     BranchItem *prev = NULL;
1906     nextBranch(cur, prev);
1907     while (cur) {
1908         c++;
1909         nextBranch(cur, prev);
1910     }
1911     return c;
1912 }
1913
1914 int VymModel::centerCount() { return rootItem->branchCount(); }
1915
1916 void VymModel::setSortFilter(const QString &s)
1917 {
1918     sortFilter = s;
1919     emit(sortFilterChanged(sortFilter));
1920 }
1921
1922 QString VymModel::getSortFilter() { return sortFilter; }
1923
1924 void VymModel::setHeading(const VymText &vt, BranchItem *bi)
1925 {
1926     Heading h_old;
1927     Heading h_new;
1928     h_new = vt;
1929     QString s = vt.getTextASCII();
1930     if (!bi)
1931         bi = getSelectedBranch();
1932     if (bi) {
1933         h_old = bi->getHeading();
1934         if (h_old == h_new)
1935             return;
1936         saveState(bi, "parseVymText (\"" + quoteQuotes(h_old.saveToDir()) + "\")", bi,
1937                   "parseVymText (\"" + quoteQuotes(h_new.saveToDir()) + "\")",
1938                   QString("Set heading of %1 to \"%2\"")
1939                       .arg(getObjectName(bi))
1940                       .arg(s));
1941         bi->setHeading(vt);
1942         emitDataChanged(bi);
1943         emitUpdateQueries();
1944         mainWindow->updateHeadingEditor(bi);    // Update HeadingEditor with new heading
1945         reposition();
1946     }
1947 }
1948
1949 void VymModel::setHeadingPlainText(const QString &s, BranchItem *bi)
1950 {
1951     if (!bi)
1952         bi = getSelectedBranch();
1953     if (bi) {
1954         VymText vt = bi->getHeading();
1955         vt.setPlainText(s);
1956         if (bi->getHeading() == vt)
1957             return;
1958         setHeading(vt, bi);
1959
1960         // Set URL
1961         if ((s.startsWith("http://") || s.startsWith("https://")) &&
1962             bi->getURL().isEmpty())
1963             setURL(s);
1964     }
1965 }
1966
1967 Heading VymModel::getHeading()
1968 {
1969     TreeItem *selti = getSelectedItem();
1970     if (selti)
1971         return selti->getHeading();
1972     qWarning() << "VymModel::getHeading Nothing selected.";
1973     return Heading();
1974 }
1975
1976 void VymModel::updateNoteText(const VymText &vt)
1977 {
1978     bool editorStateChanged = false;
1979
1980     TreeItem *selti = getSelectedItem();
1981     if (selti) {
1982         VymNote note_old = selti->getNote();
1983         VymNote note_new(vt);
1984         if (note_new.getText() != note_old.getText()) {
1985             if ((note_new.isEmpty() && !note_old.isEmpty()) ||
1986                 (!note_new.isEmpty() && note_old.isEmpty()))
1987                 editorStateChanged = true;
1988
1989             VymNote vn;
1990             vn.copy(vt);
1991
1992             saveState(selti, "parseVymText (\"" + quoteQuotes(note_old.saveToDir()) + "\")",
1993                       selti, "parseVymText (\"" + quoteQuotes(note_new.saveToDir()) + "\")",
1994                       QString("Set note of %1 to \"%2\"")
1995                           .arg(getObjectName(selti))
1996                           .arg(note_new.getTextASCII().left(20)));
1997
1998             selti->setNote(vn);
1999         }
2000
2001         // Update also flags after changes in NoteEditor
2002         emitDataChanged(selti);
2003
2004         // Only update flag, if state has changed
2005         if (editorStateChanged)
2006             reposition();
2007     }
2008 }
2009
2010 void VymModel::setNote(const VymNote &vn)
2011 {
2012     TreeItem *selti = getSelectedItem();
2013     if (selti) {
2014         VymNote n_old;
2015         VymNote n_new;
2016         n_old = selti->getNote();
2017         n_new = vn;
2018         saveState(selti, "parseVymText (\"" + quoteQuotes(n_old.saveToDir()) + "\")", selti,
2019                   "parseVymText (\"" + quoteQuotes(n_new.saveToDir()) + "\")",
2020                   QString("Set note of %1 to \"%2\"")
2021                       .arg(getObjectName(selti))
2022                       .arg(n_new.getTextASCII().left(40)));
2023         selti->setNote(n_new);
2024         emitNoteChanged(selti);
2025         emitDataChanged(selti);
2026     }
2027 }
2028
2029 VymNote VymModel::getNote()
2030 {
2031     TreeItem *selti = getSelectedItem();
2032     if (selti) {
2033         VymNote n = selti->getNote();
2034         return n;
2035     }
2036     qWarning() << "VymModel::getNote Nothing selected.";
2037     return VymNote();
2038 }
2039
2040 bool VymModel::hasRichTextNote()
2041 {
2042     TreeItem *selti = getSelectedItem();
2043     if (selti) {
2044         return selti->getNote().isRichText();
2045     }
2046     return false;
2047 }
2048
2049 void VymModel::loadNote(const QString &fn)
2050 {
2051     BranchItem *selbi = getSelectedBranch();
2052     if (selbi) {
2053         QString n;
2054         if (!loadStringFromDisk(fn, n))
2055             qWarning() << "VymModel::loadNote Couldn't load " << fn;
2056         else {
2057             VymNote vn;
2058             vn.setAutoText(n);
2059             setNote(vn);
2060             emitDataChanged(selbi);
2061             emitUpdateQueries();
2062             reposition();
2063         }
2064     }
2065     else
2066         qWarning("VymModel::loadNote no branch selected");
2067 }
2068
2069 void VymModel::saveNote(const QString &fn)
2070 {
2071     BranchItem *selbi = getSelectedBranch();
2072     if (selbi) {
2073         VymNote n = selbi->getNote();
2074         if (n.isEmpty())
2075             qWarning() << "VymModel::saveNote  note is empty, won't save to "
2076                        << fn;
2077         else {
2078             if (!saveStringToDisk(fn, n.saveToDir()))
2079                 qWarning() << "VymModel::saveNote Couldn't save " << fn;
2080         }
2081     }
2082     else
2083         qWarning("VymModel::saveNote no branch selected");
2084 }
2085
2086 void VymModel::findDuplicateURLs() // FIXME-3 needs GUI
2087 {
2088     // Generate map containing _all_ URLs and branches
2089     QString u;
2090     QMultiMap<QString, BranchItem *> map;
2091     BranchItem *cur = NULL;
2092     BranchItem *prev = NULL;
2093     nextBranch(cur, prev);
2094     while (cur) {
2095         u = cur->getURL();
2096         if (!u.isEmpty())
2097             map.insert(u, cur);
2098         nextBranch(cur, prev);
2099     }
2100
2101     // Extract duplicate URLs
2102     QMultiMap<QString, BranchItem *>::const_iterator i = map.constBegin();
2103     QMultiMap<QString, BranchItem *>::const_iterator firstdup =
2104         map.constEnd(); // invalid
2105     while (i != map.constEnd()) {
2106         if (i != map.constBegin() && i.key() == firstdup.key()) {
2107             if (i - 1 == firstdup) {
2108                 qDebug() << firstdup.key();
2109                 qDebug() << " - " << firstdup.value() << " - "
2110                          << firstdup.value()->getHeading().getText();
2111             }
2112             qDebug() << " - " << i.value() << " - "
2113                      << i.value()->getHeading().getText();
2114         }
2115         else
2116             firstdup = i;
2117
2118         ++i;
2119     }
2120 }
2121
2122 bool VymModel::findAll(FindResultModel *rmodel, QString s,
2123                        Qt::CaseSensitivity cs, bool searchNotes)
2124 {
2125     rmodel->clear();
2126     rmodel->setSearchString(s);
2127     rmodel->setSearchFlags(QTextDocument::FindFlags()); // FIXME-4 translate cs to
2128                                // QTextDocument::FindFlag
2129     bool hit = false;
2130
2131     BranchItem *cur = NULL;
2132     BranchItem *prev = NULL;
2133     nextBranch(cur, prev);
2134
2135     FindResultItem *lastParent = NULL;
2136     while (cur) {
2137         lastParent = NULL;
2138         if (cur->getHeading().getTextASCII().contains(s, cs)) {
2139             lastParent = rmodel->addItem(cur);
2140             hit = true;
2141         }
2142
2143         if (searchNotes) {
2144             QString n = cur->getNoteASCII();
2145             int i = 0;
2146             int j = 0;
2147             while (i >= 0) {
2148                 i = n.indexOf(s, i, cs);
2149                 if (i >= 0) {
2150                     // If not there yet, add "parent" item
2151                     if (!lastParent) {
2152                         lastParent = rmodel->addItem(cur);
2153                         hit = true;
2154                         if (!lastParent)
2155                             qWarning()
2156                                 << "VymModel::findAll still no lastParent?!";
2157                         /*
2158                         else
2159                             lastParent->setSelectable (false);
2160                         */
2161                     }
2162
2163                     // save index of occurence
2164                     QString e = n.mid(i - 15, 30);
2165                     n.replace('\n', ' ');
2166                     rmodel->addSubItem(
2167                         lastParent,
2168                         QString(tr("Note", "FindAll in VymModel") +
2169                                 ": \"...%1...\"")
2170                             .arg(n.mid(i - 8, 80)),
2171                         cur, j);
2172                     j++;
2173                     i++;
2174                 }
2175             }
2176         }
2177         nextBranch(cur, prev);
2178     }
2179     return hit;
2180 }
2181
2182 void VymModel::setURL(QString url, bool updateFromCloud, BranchItem *bi)
2183 {
2184     if (!bi) bi = getSelectedBranch();
2185     if (bi->getURL() == url)
2186         return;
2187
2188     if (bi) {
2189         QString oldurl = bi->getURL();
2190         bi->setURL(url);
2191         saveState(
2192             bi, QString("setURL (\"%1\")").arg(oldurl), bi,
2193             QString("setURL (\"%1\")").arg(url),
2194             QString("set URL of %1 to %2").arg(getObjectName(bi)).arg(url));
2195         emitDataChanged(bi);
2196         reposition();
2197
2198         if (updateFromCloud)    // FIXME-2 use oembed.com also for Youtube and other cloud providers
2199             // Check for Confluence
2200             setHeadingConfluencePageName();
2201     }
2202 }
2203
2204 QString VymModel::getURL()
2205 {
2206     TreeItem *selti = getSelectedItem();
2207     if (selti)
2208         return selti->getURL();
2209     else
2210         return QString();
2211 }
2212
2213 QStringList VymModel::getURLs(bool ignoreScrolled)
2214 {
2215     QStringList urls;
2216     BranchItem *selbi = getSelectedBranch();
2217     BranchItem *cur = NULL;
2218     BranchItem *prev = NULL;
2219     nextBranch(cur, prev, true, selbi);
2220     while (cur) {
2221         if (!cur->getURL().isEmpty() &&
2222             !(ignoreScrolled && cur->hasScrolledParent()))
2223             urls.append(cur->getURL());
2224         nextBranch(cur, prev, true, selbi);
2225     }
2226     return urls;
2227 }
2228
2229 void VymModel::setFrameType(const FrameObj::FrameType &t)
2230 {
2231     BranchItem *bi = getSelectedBranch();
2232     if (bi) {
2233         BranchObj *bo = (BranchObj *)(bi->getLMO());
2234         if (bo) {
2235             QString s = bo->getFrameTypeName();
2236             bo->setFrameType(t);
2237             saveState(
2238                 bi, QString("setFrameType (\"%1\")").arg(s), bi,
2239                 QString("setFrameType (\"%1\")").arg(bo->getFrameTypeName()),
2240                 QString("set type of frame to %1").arg(s));
2241             reposition();
2242             bo->updateLinkGeometry();
2243         }
2244     }
2245 }
2246
2247 void VymModel::setFrameType(const QString &s)
2248 {
2249     BranchItem *bi = getSelectedBranch();
2250     if (bi) {
2251         BranchObj *bo = (BranchObj *)(bi->getLMO());
2252         if (bo) {
2253             saveState(
2254                 bi,
2255                 QString("setFrameType (\"%1\")").arg(bo->getFrameTypeName()),
2256                 bi, QString("setFrameType (\"%1\")").arg(s),
2257                 QString("set type of frame to %1").arg(s));
2258             bo->setFrameType(s);
2259             reposition();
2260             bo->updateLinkGeometry();
2261         }
2262     }
2263 }
2264
2265 void VymModel::toggleFrameIncludeChildren()
2266 {
2267     BranchItem *bi = getSelectedBranch();
2268     if (bi) {
2269         bool b = bi->getFrameIncludeChildren();
2270         setFrameIncludeChildren(!b);
2271     }
2272 }
2273
2274 void VymModel::setFrameIncludeChildren(bool b)
2275 {
2276     BranchItem *bi = getSelectedBranch();
2277     if (bi) {
2278         QString u = b ? "false" : "true";
2279         QString r = !b ? "false" : "true";
2280
2281         saveState(bi, QString("setFrameIncludeChildren(%1)").arg(u), bi,
2282                   QString("setFrameIncludeChildren(%1)").arg(r),
2283                   QString("Include children in %1").arg(getObjectName(bi)));
2284         bi->setFrameIncludeChildren(b);
2285         emitDataChanged(bi);
2286         reposition();
2287     }
2288 }
2289
2290 void VymModel::setFramePenColor(
2291     const QColor &c) // FIXME-4 not saved if there is no LMO
2292
2293 {
2294     BranchItem *bi = getSelectedBranch();
2295     if (bi) {
2296         BranchObj *bo = (BranchObj *)(bi->getLMO());
2297         if (bo) {
2298             saveState(bi,
2299                       QString("setFramePenColor (\"%1\")")
2300                           .arg(bo->getFramePenColor().name()),
2301                       bi, QString("setFramePenColor (\"%1\")").arg(c.name()),
2302                       QString("set pen color of frame to %1").arg(c.name()));
2303             bo->setFramePenColor(c);
2304         }
2305     }
2306 }
2307
2308 void VymModel::setFrameBrushColor(
2309     const QColor &c) // FIXME-4 not saved if there is no LMO
2310 {
2311     BranchItem *bi = getSelectedBranch();
2312     if (bi) {
2313         BranchObj *bo = (BranchObj *)(bi->getLMO());
2314         if (bo) {
2315             saveState(bi,
2316                       QString("setFrameBrushColor (\"%1\")")
2317                           .arg(bo->getFrameBrushColor().name()),
2318                       bi, QString("setFrameBrushColor (\"%1\")").arg(c.name()),
2319                       QString("set brush color of frame to %1").arg(c.name()));
2320             bo->setFrameBrushColor(c);
2321             bi->setBackgroundColor(c); // FIXME-4 redundant with above
2322         }
2323         emitDataChanged(bi);  // Notify HeadingEditor to eventually change BG color
2324     }
2325 }
2326
2327 void VymModel::setFramePadding(
2328     const int &i)
2329 {
2330     BranchItem *bi = getSelectedBranch();
2331     if (bi) {
2332         BranchObj *bo = (BranchObj *)(bi->getLMO());
2333         if (bo) {
2334             saveState(
2335                 bi,
2336                 QString("setFramePadding (\"%1\")").arg(bo->getFramePadding()),
2337                 bi, QString("setFramePadding (\"%1\")").arg(i),
2338                 QString("set brush color of frame to %1").arg(i));
2339             bo->setFramePadding(i);
2340             reposition();
2341             bo->updateLinkGeometry();
2342         }
2343     }
2344 }
2345
2346 void VymModel::setFrameBorderWidth(
2347     const int &i)
2348 {
2349     BranchItem *bi = getSelectedBranch();
2350     if (bi) {
2351         BranchObj *bo = (BranchObj *)(bi->getLMO());
2352         if (bo) {
2353             saveState(bi,
2354                       QString("setFrameBorderWidth (\"%1\")")
2355                           .arg(bo->getFrameBorderWidth()),
2356                       bi, QString("setFrameBorderWidth (\"%1\")").arg(i),
2357                       QString("set border width of frame to %1").arg(i));
2358             bo->setFrameBorderWidth(i);
2359             reposition();
2360             bo->updateLinkGeometry();
2361         }
2362     }
2363 }
2364
2365 void VymModel::setIncludeImagesVer(bool b)
2366 {
2367     BranchItem *bi = getSelectedBranch();
2368     if (bi && b != bi->getIncludeImagesVer()) {
2369         QString u = b ? "false" : "true";
2370         QString r = !b ? "false" : "true";
2371
2372         saveState(
2373             bi, QString("setIncludeImagesVertically (%1)").arg(u), bi,
2374             QString("setIncludeImagesVertically (%1)").arg(r),
2375             QString("Include images vertically in %1").arg(getObjectName(bi)));
2376         bi->setIncludeImagesVer(b);
2377         emitDataChanged(bi);
2378         reposition();
2379     }
2380 }
2381
2382 void VymModel::setIncludeImagesHor(bool b)
2383 {
2384     BranchItem *bi = getSelectedBranch();
2385     if (bi && b != bi->getIncludeImagesHor()) {
2386         QString u = b ? "false" : "true";
2387         QString r = !b ? "false" : "true";
2388
2389         saveState(bi, QString("setIncludeImagesHorizontally (%1)").arg(u), bi,
2390                   QString("setIncludeImagesHorizontally (%1)").arg(r),
2391                   QString("Include images horizontally in %1")
2392                       .arg(getObjectName(bi)));
2393         bi->setIncludeImagesHor(b);
2394         emitDataChanged(bi);
2395         reposition();
2396     }
2397 }
2398
2399 void VymModel::setChildrenLayout(
2400     BranchItem::LayoutHint layoutHint) // FIXME-3 no savestate yet
2401 {
2402     BranchItem *bi = getSelectedBranch();
2403     if (bi) {
2404         /*
2405         QString u= b ? "false" : "true";
2406         QString r=!b ? "false" : "true";
2407
2408         saveState(
2409             bi,
2410             QString("setIncludeImagesHorizontally (%1)").arg(u),
2411             bi,
2412             QString("setIncludeImagesHorizontally (%1)").arg(r),
2413             QString("Include images horizontally in %1").arg(getObjectName(bi))
2414         );
2415         */
2416         bi->setChildrenLayout(layoutHint);
2417         emitDataChanged(bi);
2418         reposition();
2419     }
2420 }
2421
2422 void VymModel::setHideLinkUnselected(bool b)
2423 {
2424     TreeItem *ti = getSelectedItem();
2425     if (ti && (ti->getType() == TreeItem::Image || ti->isBranchLikeType())) {
2426         QString u = b ? "false" : "true";
2427         QString r = !b ? "false" : "true";
2428
2429         saveState(
2430             ti, QString("setHideLinkUnselected (%1)").arg(u), ti,
2431             QString("setHideLinkUnselected (%1)").arg(r),
2432             QString("Hide link of %1 if unselected").arg(getObjectName(ti)));
2433         ((MapItem *)ti)->setHideLinkUnselected(b);
2434     }
2435 }
2436
2437 void VymModel::setHideExport(bool b, TreeItem *ti)
2438 {
2439     if (!ti)
2440         ti = getSelectedItem();
2441     if (ti && (ti->getType() == TreeItem::Image || ti->isBranchLikeType()) &&
2442         ti->hideInExport() != b) {
2443         ti->setHideInExport(b);
2444         QString u = b ? "false" : "true";
2445         QString r = !b ? "false" : "true";
2446
2447         saveState(ti, QString("setHideExport (%1)").arg(u), ti,
2448                   QString("setHideExport (%1)").arg(r),
2449                   QString("Set HideExport flag of %1 to %2")
2450                       .arg(getObjectName(ti))
2451                       .arg(r));
2452         emitDataChanged(ti);
2453         emitSelectionChanged();
2454         reposition();
2455     }
2456 }
2457
2458 void VymModel::toggleHideExport()
2459 {
2460     QList<TreeItem *> selItems = getSelectedItems();
2461     if (selItems.count() > 0) {
2462         foreach (TreeItem *ti, selItems) {
2463             bool b = !ti->hideInExport();
2464             setHideExport(b, ti);
2465         }
2466     }
2467 }
2468
2469 void VymModel::toggleTask()
2470 {
2471     BranchItem *selbi = getSelectedBranch();
2472     if (selbi) {
2473         saveStateChangingPart(
2474             selbi, selbi, QString("toggleTask()"),
2475             QString("Toggle task of %1").arg(getObjectName(selbi)));
2476         Task *task = selbi->getTask();
2477         if (!task) {
2478             task = taskModel->createTask(selbi);
2479             taskEditor->select(task);
2480         }
2481         else
2482             taskModel->deleteTask(task);
2483
2484         emitDataChanged(selbi);
2485         emitSelectionChanged();
2486         reposition();
2487     }
2488 }
2489
2490 bool VymModel::cycleTaskStatus(bool reverse)
2491 {
2492     BranchItem *selbi = getSelectedBranch();
2493     if (selbi) {
2494         Task *task = selbi->getTask();
2495         if (task) {
2496             saveStateChangingPart(
2497                 selbi, selbi, QString("cycleTask()"),
2498                 QString("Toggle task of %1").arg(getObjectName(selbi)));
2499             task->cycleStatus(reverse);
2500             task->setDateModification();
2501
2502             // make sure task is still visible
2503             taskEditor->select(task);
2504             emitDataChanged(selbi);
2505             reposition();
2506             return true;
2507         }
2508     }
2509     return false;
2510 }
2511
2512 bool VymModel::setTaskSleep(const QString &s)
2513 {
2514     bool ok = false;
2515     BranchItem *selbi = getSelectedBranch();
2516     if (selbi && !s.isEmpty()) {
2517         Task *task = selbi->getTask();
2518         if (task) {
2519             QDateTime oldSleep = task->getSleep();
2520
2521             // Parse the string, which could be days, hours or one of several
2522             // time formats
2523
2524             if (s == "0") {
2525                 ok = task->setSecsSleep(0);
2526             }
2527             else {
2528                 QRegExp re("^\\s*(\\d+)\\s*$");
2529                 re.setMinimal(false);
2530                 int pos = re.indexIn(s);
2531                 if (pos >= 0) {
2532                     // Found only digit, considered as days
2533                     ok = task->setDaysSleep(re.cap(1).toInt());
2534                 }
2535                 else {
2536                     QRegExp re("^\\s*(\\d+)\\s*h\\s*$");
2537                     pos = re.indexIn(s);
2538                     if (pos >= 0) {
2539                         // Found digit followed by "h", considered as hours
2540                         ok = task->setHoursSleep(re.cap(1).toInt());
2541                     }
2542                     else {
2543                         QRegExp re("^\\s*(\\d+)\\s*w\\s*$");
2544                         pos = re.indexIn(s);
2545                         if (pos >= 0) {
2546                             // Found digit followed by "w", considered as weeks
2547                             ok = task->setDaysSleep(7 * re.cap(1).toInt());
2548                         }
2549                         else {
2550                             QRegExp re("^\\s*(\\d+)\\s*s\\s*$");
2551                             pos = re.indexIn(s);
2552                             if (pos >= 0) {
2553                                 // Found digit followed by "s", considered as
2554                                 // seconds
2555                                 ok = task->setSecsSleep(re.cap(1).toInt());
2556                             }
2557                             else {
2558                                 ok = task->setDateSleep(
2559                                     s); // ISO date YYYY-MM-DDTHH:mm:ss
2560
2561                                 if (!ok) {
2562                                     QRegExp re("(\\d+)\\.(\\d+)\\.(\\d+)");
2563                                     re.setMinimal(false);
2564                                     int pos = re.indexIn(s);
2565                                     QStringList list = re.capturedTexts();
2566                                     QDateTime d;
2567                                     if (pos >= 0) {
2568                                         d = QDateTime(
2569                                             QDate(list.at(3).toInt(),
2570                                                   list.at(2).toInt(),
2571                                                   list.at(1).toInt()).startOfDay());
2572                                         // d = QDate(list.at(3).toInt(),
2573                                         // list.at(2).toInt(),
2574                                         // list.at(1).toInt()).startOfDay();
2575                                         ok = task->setDateSleep(
2576                                             d); // German format,
2577                                                 // e.g. 24.12.2012
2578                                     }
2579                                     else {
2580                                         re.setPattern("(\\d+)\\.(\\d+)\\.");
2581                                         pos = re.indexIn(s);
2582                                         list = re.capturedTexts();
2583                                         if (pos >= 0) {
2584                                             int month = list.at(2).toInt();
2585                                             int day = list.at(1).toInt();
2586                                             int year =
2587                                                 QDate::currentDate().year();
2588                                             d = QDateTime(
2589                                                 QDate(year, month, day).startOfDay());
2590                                             // d = QDate(year, month,
2591                                             // day).startOfDay();
2592                                             if (QDateTime::currentDateTime()
2593                                                     .daysTo(d) < 0) {
2594                                                 year++;
2595                                                 d = QDateTime(
2596                                                     QDate(year, month, day).startOfDay());
2597                                                 // d = QDate(year, month,
2598                                                 // day).startOfDay();
2599                                             }
2600                                             ok = task->setDateSleep(
2601                                                 d); // Short German format,
2602                                                     // e.g. 24.12.
2603                                         }
2604                                         else {
2605                                             re.setPattern("(\\d+)\\:(\\d+)");
2606                                             pos = re.indexIn(s);
2607                                             list = re.capturedTexts();
2608                                             if (pos >= 0) {
2609                                                 int hour = list.at(1).toInt();
2610                                                 int min = list.at(2).toInt();
2611                                                 d = QDateTime(
2612                                                     QDate::currentDate(),
2613                                                     QTime(hour, min));
2614                                                 ok = task->setDateSleep(
2615                                                     d); // Time HH:MM
2616                                             }
2617                                         }
2618                                     }
2619                                 }
2620                             }
2621                         }
2622                     }
2623                 }
2624             }
2625
2626             if (ok) {
2627                 QString oldSleepString;
2628                 if (oldSleep.isValid())
2629                     oldSleepString = oldSleep.toString(Qt::ISODate);
2630                 else
2631                     oldSleepString =
2632                         "1970-01-26T00:00:00"; // Some date long ago
2633
2634                 QString newSleepString = task->getSleep().toString(Qt::ISODate);
2635                 task->setDateModification();
2636                 selbi->updateTaskFlag(); // If tasks changes awake mode, then
2637                                          // flag needs to change
2638                 saveState(
2639                     selbi, QString("setTaskSleep (\"%1\")").arg(oldSleepString),
2640                     selbi, QString("setTaskSleep (\"%1\")").arg(newSleepString),
2641                     QString("setTaskSleep (\"%1\")").arg(newSleepString));
2642                 emitDataChanged(selbi);
2643                 reposition();
2644             }
2645
2646         } // Found task
2647     }     // Found branch
2648     return ok;
2649 }
2650
2651 void VymModel::setTaskPriorityDelta(const int &pd, BranchItem *bi)
2652 {
2653     QList<BranchItem *> selbis;
2654     if (bi)
2655         selbis << bi;
2656     else
2657         selbis = getSelectedBranches();
2658
2659     foreach (BranchItem *selbi, selbis) {
2660         Task *task = selbi->getTask();
2661         if (task) {
2662             saveState(selbi,
2663                       QString("setTaskPriorityDelta (%1)")
2664                           .arg(task->getPriorityDelta()),
2665                       selbi,
2666                       QString("setTaskPriorityDelta (%1)")
2667                           .arg(pd),
2668                       "Set delta for priority of task");
2669             task->setPriorityDelta(pd);
2670             emitDataChanged(selbi);
2671         }
2672     }
2673 }
2674
2675 int VymModel::getTaskPriorityDelta()
2676 {
2677     BranchItem *selbi = getSelectedBranch();
2678     if (selbi) {
2679         Task *task = selbi->getTask();
2680         if (task)
2681             return task->getPriorityDelta();
2682     }
2683     return 0;
2684 }
2685
2686 int VymModel::taskCount() { return taskModel->count(this); }
2687
2688 void VymModel::updateTasksAlarm(bool force)
2689 {
2690     if (taskModel->updateAwake(force) || force) {
2691         reposition();
2692     }
2693 }
2694
2695 BranchItem *VymModel::addTimestamp()
2696 {
2697     BranchItem *selbi = addNewBranch();
2698     if (selbi) {
2699         QDate today = QDate::currentDate();
2700         QChar c = '0';
2701         selbi->setHeadingPlainText(QString("%1-%2-%3")
2702                                        .arg(today.year(), 4, 10, c)
2703                                        .arg(today.month(), 2, 10, c)
2704                                        .arg(today.day(), 2, 10, c));
2705         emitDataChanged(selbi);
2706         reposition();
2707         select(selbi);
2708     }
2709     return selbi;
2710 }
2711
2712 void VymModel::copy()
2713 {
2714     if (readonly)
2715         return;
2716
2717     QList<TreeItem *> itemList = getSelectedItems();
2718
2719     QStringList clipboardFiles;
2720
2721     if (itemList.count() > 0) {
2722         uint i = 1;
2723         QString fn;
2724         foreach (TreeItem *ti, itemList) {
2725             fn = QString("%1/%2-%3.xml")
2726                      .arg(clipboardDir)
2727                      .arg(clipboardFile)
2728                      .arg(i);
2729             QString content = saveToDir(clipboardDir, clipboardFile,
2730                                         FlagRowMaster::NoFlags, QPointF(), ti);
2731
2732             if (!saveStringToDisk(fn, content))
2733                 qWarning() << "ME::saveStringToDisk failed: " << fn;
2734             else {
2735                 i++;
2736                 clipboardFiles.append(fn);
2737             }
2738         }
2739         QClipboard *clipboard = QApplication::clipboard();
2740         QMimeData *mimeData = new QMimeData;
2741         mimeData->setData("application/x-vym", clipboardFiles.join(",").toLatin1());
2742         clipboard->setMimeData(mimeData);
2743     }
2744 }
2745
2746 void VymModel::paste()
2747 {
2748     if (readonly)
2749         return;
2750
2751     BranchItem *selbi = getSelectedBranch();
2752
2753     if (selbi) {
2754         const QClipboard *clipboard = QApplication::clipboard();
2755         const QMimeData *mimeData = clipboard->mimeData();
2756
2757         if (mimeData->formats().contains("application/x-vym")) {
2758             QStringList clipboardFiles = QString(mimeData->data("application/x-vym")).split(",");
2759
2760             saveStateChangingPart(selbi, selbi, QString("paste ()"),
2761                                   QString("Paste"));
2762
2763             bool zippedOrg = zipped;
2764             foreach(QString fn, clipboardFiles) {
2765                 if (File::Success != loadMap(fn, ImportAdd, VymMap, SlideContent))
2766                     qWarning() << "VM::paste Loading clipboard failed: " << fn;
2767             }
2768             zipped = zippedOrg;
2769             reposition();
2770         } else if (mimeData->hasImage()) {
2771             QImage image = qvariant_cast<QImage>(mimeData->imageData());
2772             QString fn = clipboardDir + "/" + "image.png";
2773             if (!image.save(fn))
2774                 qWarning() << "VM::paste  Could not save copy of image in system clipboard";
2775             else {
2776                 ImageItem *ii = loadImage(selbi, fn);
2777                 if (ii)
2778                     setScaleFactor(300.0 / image.width(), ii);    // FIXME-2 Better use user-defined fixed width
2779             }
2780         } else if (mimeData->hasHtml()) {
2781             //setText(mimeData->html());
2782             //setTextFormat(Qt::RichText);
2783             qDebug() << "VM::paste found html...";
2784         } else if (mimeData->hasText()) {
2785             //setText(mimeData->text());
2786             //setTextFormat(Qt::PlainText);
2787             qDebug() << "VM::paste found text...";
2788         } else {
2789             qWarning() << "VM::paste Cannot paste data, mimeData->formats=" << mimeData->formats();
2790         }
2791     }
2792 }
2793
2794 void VymModel::cut()
2795 {
2796     if (readonly)
2797         return;
2798
2799     copy();
2800     deleteSelection();
2801 }
2802
2803 bool VymModel::moveUp(BranchItem *bi)
2804 {
2805     if (readonly)
2806         return false;
2807
2808     bool oldState = saveStateBlocked;
2809     saveStateBlocked = true;
2810     bool result = false;
2811     if (bi && bi->canMoveUp())
2812         result =
2813             relinkBranch(bi, (BranchItem *)bi->parent(), bi->num() - 1, false);
2814     saveStateBlocked = oldState;
2815     return result;
2816 }
2817
2818 void VymModel::moveUp()
2819 {
2820     BranchItem *selbi = getSelectedBranch();
2821     if (selbi) {
2822         QString oldsel = getSelectString(selbi);
2823         if (moveUp(selbi)) {
2824             saveState(getSelectString(selbi), "moveDown ()", oldsel,
2825                       "moveUp ()",
2826                       QString("Move up %1").arg(getObjectName(selbi)));
2827             select(selbi);
2828         }
2829     }
2830 }
2831
2832 bool VymModel::moveDown(BranchItem *bi)
2833 {
2834     if (readonly)
2835         return false;
2836
2837     bool oldState = saveStateBlocked;
2838     saveStateBlocked = true;
2839     bool result = false;
2840     if (bi && bi->canMoveDown())
2841         result =
2842             relinkBranch(bi, (BranchItem *)bi->parent(), bi->num() + 1, false);
2843     saveStateBlocked = oldState;
2844     return result;
2845 }
2846
2847 void VymModel::moveDown()
2848 {
2849     BranchItem *selbi = getSelectedBranch();
2850     if (selbi) {
2851         QString oldsel = getSelectString(selbi);
2852         if (moveDown(selbi)) {
2853             saveState(getSelectString(selbi), "moveUp ()", oldsel,
2854                       "moveDown ()",
2855                       QString("Move down %1").arg(getObjectName(selbi)));
2856             select(selbi);
2857         }
2858     }
2859 }
2860
2861 void VymModel::moveUpDiagonally()
2862 {
2863     BranchItem *selbi = getSelectedBranch();
2864     if (selbi) {
2865         BranchItem *parent = selbi->parentBranch();
2866         if (parent == rootItem) return;
2867
2868         int n = selbi->num();
2869         if (n == 0) return;
2870
2871         BranchItem *dst = parent->getBranchNum(n-1);
2872         if (!dst) return;
2873
2874         relinkBranch(selbi, dst, dst->branchCount() + 1, true);
2875      }
2876 }
2877
2878 void VymModel::moveDownDiagonally()
2879 {
2880     BranchItem *selbi = getSelectedBranch();
2881     if (selbi) {
2882         BranchItem *parent = selbi->parentBranch();
2883         if (parent == rootItem) return;
2884         BranchItem *parentParent = parent->parentBranch();
2885         int n = parent->num();
2886
2887         relinkBranch(selbi, parentParent, n + 1, true);
2888      }
2889 }
2890
2891 void VymModel::detach()
2892 {
2893     BranchItem *selbi = getSelectedBranch();
2894     if (selbi && selbi->depth() > 0) {
2895         // if no relPos have been set before, try to use current rel positions
2896         if (selbi->getLMO())
2897             for (int i = 0; i < selbi->branchCount(); ++i)
2898                 selbi->getBranchNum(i)->getBranchObj()->setRelPos();
2899
2900         QString oldsel = getSelectString();
2901         int n = selbi->num();
2902         QPointF p;
2903         BranchObj *bo = selbi->getBranchObj();
2904         if (bo)
2905             p = bo->getAbsPos();
2906         QString parsel = getSelectString(selbi->parent());
2907         if (relinkBranch(selbi, rootItem, -1, true))
2908             saveState(getSelectString(selbi),
2909                       QString("relinkTo (\"%1\",%2,%3,%4)")
2910                           .arg(parsel)
2911                           .arg(n)
2912                           .arg(p.x())
2913                           .arg(p.y()),
2914                       oldsel, "detach ()",
2915                       QString("Detach %1").arg(getObjectName(selbi)));
2916     }
2917 }
2918
2919 void VymModel::sortChildren(bool inverse)
2920 {
2921     BranchItem *selbi = getSelectedBranch();
2922     if (selbi) {
2923         if (selbi->branchCount() > 1) {
2924             if (!inverse)
2925                 saveStateChangingPart(
2926                     selbi, selbi, "sortChildren ()",
2927                     QString("Sort children of %1").arg(getObjectName(selbi)));
2928             else
2929                 saveStateChangingPart(selbi, selbi, "sortChildren (false)",
2930                                       QString("Inverse sort children of %1")
2931                                           .arg(getObjectName(selbi)));
2932
2933             selbi->sortChildren(inverse);
2934             select(selbi);
2935             reposition();
2936         }
2937     }
2938 }
2939
2940 BranchItem *VymModel::createMapCenter()
2941 {
2942     BranchItem *newbi = addMapCenter(QPointF(0, 0));
2943     return newbi;
2944 }
2945
2946 BranchItem *VymModel::createBranch(BranchItem *dst)
2947 {
2948     if (dst)
2949         return addNewBranchInt(dst, -2);
2950     else
2951         return NULL;
2952 }
2953
2954 ImageItem *VymModel::createImage(BranchItem *dst)
2955 {
2956     if (dst) {
2957         QModelIndex parix;
2958         int n;
2959
2960         ImageItem *newii = new ImageItem();
2961         // newii->setHeading (QApplication::translate("Heading of new image in
2962         // map", "new image"));
2963
2964         emit(layoutAboutToBeChanged());
2965
2966         parix = index(dst);
2967         if (!parix.isValid())
2968             qDebug() << "VM::createII invalid index\n";
2969         n = dst->getRowNumAppend(newii);
2970         beginInsertRows(parix, n, n);
2971         dst->appendChild(newii);
2972         endInsertRows();
2973
2974         emit(layoutChanged());
2975
2976         // save scroll state. If scrolled, automatically select
2977         // new branch in order to tmp unscroll parent...
2978         newii->createMapObj();
2979         latestAddedItem = newii;
2980         reposition();
2981         return newii;
2982     }
2983     return NULL;
2984 }
2985
2986 bool VymModel::createLink(Link *link)
2987 {
2988     BranchItem *begin = link->getBeginBranch();
2989     BranchItem *end = link->getEndBranch();
2990
2991     if (!begin || !end) {
2992         qWarning() << "VM::createXLinkNew part of XLink is NULL";
2993         return false;
2994     }
2995
2996     if (begin == end) {
2997         if (debug)
2998             qDebug() << "VymModel::createLink begin==end, aborting";
2999         return false;
3000     }
3001
3002     // check, if link already exists
3003     foreach (Link *l, xlinks) {
3004         if ((l->getBeginBranch() == begin && l->getEndBranch() == end) ||
3005             (l->getBeginBranch() == end && l->getEndBranch() == begin)) {
3006             qWarning() << "VymModel::createLink link exists already, aborting";
3007             return false;
3008         }
3009     }
3010
3011     QModelIndex parix;
3012     int n;
3013
3014     XLinkItem *newli = new XLinkItem();
3015     newli->setLink(link);
3016     link->setBeginLinkItem(newli);
3017
3018     emit(layoutAboutToBeChanged());
3019
3020     parix = index(begin);
3021     n = begin->getRowNumAppend(newli);
3022     beginInsertRows(parix, n, n);
3023     begin->appendChild(newli);
3024     endInsertRows();
3025
3026     newli = new XLinkItem();
3027     newli->setLink(link);
3028     link->setEndLinkItem(newli);
3029
3030     parix = index(end);
3031     n = end->getRowNumAppend(newli);
3032     beginInsertRows(parix, n, n);
3033     end->appendChild(newli);
3034     endInsertRows();
3035
3036     emit(layoutChanged());
3037
3038     xlinks.append(link);
3039     link->activate();
3040
3041     latestAddedItem = newli;
3042
3043     if (!link->getMO()) {
3044         link->createMapObj();
3045         reposition();
3046     }
3047     else
3048         link->updateLink();
3049
3050     link->setStyleBegin(defXLinkStyleBegin);
3051     link->setStyleEnd(defXLinkStyleEnd);
3052     return true;
3053 }
3054
3055 QColor VymModel::getXLinkColor()
3056 {
3057     Link *l = getSelectedXLink();
3058     if (l)
3059         return l->getPen().color();
3060     else
3061         return QColor();
3062 }
3063
3064 int VymModel::getXLinkWidth()
3065 {
3066     Link *l = getSelectedXLink();
3067     if (l)
3068         return l->getPen().width();
3069     else
3070         return -1;
3071 }
3072
3073 Qt::PenStyle VymModel::getXLinkStyle()
3074 {
3075     Link *l = getSelectedXLink();
3076     if (l)
3077         return l->getPen().style();
3078     else
3079         return Qt::NoPen;
3080 }
3081
3082 QString VymModel::getXLinkStyleBegin()
3083 {
3084     Link *l = getSelectedXLink();
3085     if (l)
3086         return l->getStyleBeginString();
3087     else
3088         return QString();
3089 }
3090
3091 QString VymModel::getXLinkStyleEnd()
3092 {
3093     Link *l = getSelectedXLink();
3094     if (l)
3095         return l->getStyleEndString();
3096     else
3097         return QString();
3098 }
3099
3100 AttributeItem *VymModel::setAttribute() // FIXME-3 Experimental, savestate missing
3101
3102 {
3103     BranchItem *selbi = getSelectedBranch();
3104     if (selbi) {
3105         AttributeItem *ai = new AttributeItem();
3106         ai->setAttributeType(AttributeItem::String);
3107         ai->setKey("Foo Attrib");
3108         ai->setValue(QString("Att val"));
3109
3110         return setAttribute(selbi, ai);
3111     }
3112     return nullptr;
3113 }
3114
3115 AttributeItem *VymModel::setAttribute(BranchItem *dst, AttributeItem *ai_new)
3116 {
3117     if (dst) {
3118
3119         // Check if there is already an attribute with same key
3120         AttributeItem *ai;
3121         for (int i = 0; i < dst->attributeCount(); i++) {
3122             ai = dst->getAttributeNum(i);
3123             if (ai->getKey() == ai_new->getKey()) 
3124             {
3125                 // Key exists, overwrite value
3126                 ai->copy(ai_new);
3127
3128                 // Delete original attribute, this is basically a move...
3129                 delete ai_new;
3130                 emitDataChanged(dst);
3131                 return ai;
3132             }
3133         }
3134
3135         // Create new attribute
3136         emit(layoutAboutToBeChanged());
3137
3138         QModelIndex parix = index(dst);
3139         int n = dst->getRowNumAppend(ai_new);
3140         beginInsertRows(parix, n, n);
3141         dst->appendChild(ai_new);
3142         endInsertRows();
3143
3144         emit(layoutChanged());
3145
3146         emitDataChanged(dst);
3147         return ai_new;  // FIXME-3 Check if ai is used or deleted - deep copy here?
3148     }
3149     return NULL;
3150 }
3151
3152 BranchItem *VymModel::addMapCenter(bool saveStateFlag)
3153 {
3154     if (!hasContextPos) {
3155         // E.g. when called via keypresss:
3156         // Place new MCO in middle of existing ones,
3157         // Useful for "brainstorming" mode...
3158         contextPos = QPointF();
3159         BranchItem *bi;
3160         BranchObj *bo;
3161         for (int i = 0; i < rootItem->branchCount(); ++i) {
3162             bi = rootItem->getBranchNum(i);
3163             bo = (BranchObj *)bi->getLMO();
3164             if (bo)
3165                 contextPos += bo->getAbsPos();
3166         }
3167         if (rootItem->branchCount() > 1)
3168             contextPos *= 1 / (qreal)(rootItem->branchCount());
3169     }
3170
3171     BranchItem *bi = addMapCenter(contextPos);
3172     updateActions();
3173     emitShowSelection();
3174     if (saveStateFlag)
3175         saveState(bi, "remove()", NULL,
3176                   QString("addMapCenter (%1,%2)")
3177                       .arg(contextPos.x())
3178                       .arg(contextPos.y()),
3179                   QString("Adding MapCenter to (%1,%2)")
3180                       .arg(contextPos.x())
3181                       .arg(contextPos.y()));
3182     emitUpdateLayout();
3183     return bi;
3184 }
3185
3186 BranchItem *VymModel::addMapCenter(QPointF absPos)
3187 // createMapCenter could then probably be merged with createBranch
3188 {
3189
3190     // Create TreeItem
3191     QModelIndex parix = index(rootItem);
3192
3193     BranchItem *newbi = new BranchItem(rootItem);
3194     newbi->setHeadingPlainText(tr("New map", "New map"));
3195     int n = rootItem->getRowNumAppend(newbi);
3196
3197     emit(layoutAboutToBeChanged());
3198     beginInsertRows(parix, n, n);
3199
3200     rootItem->appendChild(newbi);
3201
3202     endInsertRows();
3203     emit(layoutChanged());
3204
3205     // Create MapObj
3206     newbi->setPositionMode(MapItem::Absolute);
3207     BranchObj *bo = newbi->createMapObj(mapEditor->getScene());
3208     if (bo)
3209         bo->move(absPos);
3210
3211     return newbi;
3212 }
3213
3214 BranchItem *VymModel::addNewBranchInt(BranchItem *dst, int pos)
3215 {
3216     // Depending on pos:
3217     // -3       insert in children of parent  above selection
3218     // -2       add branch to selection
3219     // -1       insert in children of parent below selection
3220     // 0..n     insert in children of parent at pos
3221
3222     // Create TreeItem
3223     BranchItem *parbi = dst;
3224     int n;
3225     BranchItem *newbi = new BranchItem();
3226
3227     emit(layoutAboutToBeChanged());
3228
3229     if (pos == -2) {
3230         n = parbi->getRowNumAppend(newbi);
3231         beginInsertRows(index(parbi), n, n);
3232         parbi->appendChild(newbi);
3233         endInsertRows();
3234     }
3235     else if (pos == -1 || pos == -3) {
3236         // insert below selection
3237         parbi = (BranchItem *)dst->parent();
3238         n = dst->childNumber() + (3 + pos) / 2; //-1 |-> 1;-3 |-> 0
3239         beginInsertRows(index(parbi), n, n);
3240         parbi->insertBranch(n, newbi);
3241         endInsertRows();
3242     }
3243     else { // pos >= 0
3244         n = parbi->getRowNumAppend(newbi) - (parbi->branchCount() - pos);
3245         beginInsertRows(index(parbi), n, n);
3246         parbi->insertBranch(pos, newbi);
3247         endInsertRows();
3248     }
3249     emit(layoutChanged());
3250
3251     newbi->createMapObj(mapEditor->getScene());
3252
3253     // Set color of heading to that of parent
3254     newbi->setHeadingColor(parbi->getHeadingColor());
3255
3256     reposition();
3257     return newbi;
3258 }
3259
3260 BranchItem *VymModel::addNewBranch(BranchItem *bi, int pos)
3261 {
3262     BranchItem *newbi = NULL;
3263     if (!bi)
3264         bi = getSelectedBranch();
3265
3266     if (bi) {
3267         QString redosel = getSelectString(bi);
3268         newbi = addNewBranchInt(bi, pos);
3269         QString undosel = getSelectString(newbi);
3270
3271         if (newbi) {
3272             saveState(undosel, "remove ()", redosel,
3273                       QString("addBranch (%1)").arg(pos),
3274                       QString("Add new branch to %1").arg(getObjectName(bi)));
3275
3276             latestAddedItem = newbi;
3277             // In Network mode, the client needs to know where the new branch
3278             // is, so we have to pass on this information via saveState.
3279             // TODO: Get rid of this positioning workaround
3280             /* FIXME-4  network problem:  QString ps=qpointfToString
3281                (newbo->getAbsPos()); sendData ("selectLatestAdded ()"); sendData
3282                (QString("move %1").arg(ps)); sendSelection();
3283                */
3284         }
3285     }
3286     return newbi;
3287 }
3288
3289 BranchItem *VymModel::addNewBranchBefore()
3290 {
3291     BranchItem *newbi = NULL;
3292     BranchItem *selbi = getSelectedBranch();
3293     if (selbi && selbi->getType() == TreeItem::Branch)
3294     // We accept no MapCenter here, so we _have_ a parent
3295     {
3296         // add below selection
3297         newbi = addNewBranchInt(selbi, -1);
3298
3299         if (newbi) {
3300             saveState(
3301                 newbi, "remove ()", newbi, "addBranchBefore ()",
3302                 QString("Add branch before %1").arg(getObjectName(selbi)));
3303
3304             // newbi->move2RelPos (p);
3305
3306             // Move selection to new branch
3307             relinkBranch(selbi, newbi, 0, true);
3308
3309             // Use color of child instead of parent
3310             newbi->setHeadingColor(selbi->getHeadingColor());
3311             emitDataChanged(newbi);
3312         }
3313     }
3314     return newbi;
3315 }
3316
3317 bool VymModel::relinkBranch(BranchItem *branch, BranchItem *dst, int pos,
3318                             bool updateSelection, QPointF orgPos)
3319 {
3320     if (branch && dst) {
3321         // Check if we relink to ourselves
3322         if (dst->isChildOf(branch))
3323             return false;
3324
3325         if (updateSelection)
3326             unselectAll();
3327
3328         // Do we need to update frame type?
3329         bool keepFrame = true;
3330
3331         // Save old position for savestate
3332         QString preSelStr = getSelectString(branch);
3333         QString preNum = QString::number(branch->num(), 10);
3334         QString preParStr = getSelectString(branch->parent());
3335
3336         emit(layoutAboutToBeChanged());
3337         BranchItem *branchpi = (BranchItem *)branch->parent();
3338         // Remove at current position
3339         int n = branch->childNum();
3340
3341         // If branch and dst have same parent, then pos needs to be adjusted 
3342         // after removing branch
3343         if (branchpi == dst && pos - 1 > n ) pos--;
3344
3345         beginRemoveRows(index(branchpi), n, n);
3346         branchpi->removeChild(n);
3347         endRemoveRows();
3348
3349         if (pos < 0 || pos > dst->branchCount())
3350             pos = dst->branchCount();
3351
3352         // Append as last branch to dst
3353         if (dst->branchCount() == 0)
3354             n = 0;
3355         else
3356             n = dst->getFirstBranch()->childNumber();
3357         
3358         beginInsertRows(index(dst), n + pos, n + pos);
3359         dst->insertBranch(pos, branch);
3360         endInsertRows();
3361
3362         // Correct type if necessesary
3363         if (branch->getType() == TreeItem::MapCenter && branch->depth() > 0) {
3364             branch->setType(TreeItem::Branch);
3365             keepFrame = false;
3366         }
3367
3368         // reset parObj, fonts, frame, etc in related LMO or other view-objects
3369         branch->updateStyles(keepFrame);
3370
3371         emitDataChanged(branch);
3372         reposition(); // both for moveUp/Down and relinking
3373
3374         // Savestate
3375         QString postSelStr = getSelectString(branch);
3376         QString postNum = QString::number(branch->num(), 10);
3377
3378         QPointF savePos;
3379         LinkableMapObj *lmosel = branch->getLMO();
3380         if (lmosel)
3381             savePos = lmosel->getAbsPos();
3382
3383         if (!saveStateBlocked) { // Don't build strings when moving up/down
3384             QString undoCom =
3385                 "relinkTo (\"" + preParStr + "\"," + preNum + "," +
3386                 QString("%1,%2").arg(orgPos.x()).arg(orgPos.y()) + ")";
3387
3388             QString redoCom =
3389                 "relinkTo (\"" + getSelectString(dst) + "\"," + postNum + "," +
3390                 QString("%1,%2").arg(savePos.x()).arg(savePos.y()) + ")";
3391
3392             saveState(postSelStr, undoCom, preSelStr, redoCom,
3393                       QString("Relink %1 to %2")
3394                           .arg(getObjectName(branch))
3395                           .arg(getObjectName(dst)));
3396         }
3397
3398         // New parent might be invisible
3399         branch->updateVisibility();
3400
3401         if (dst->isScrolled()) {
3402             if (updateSelection)
3403                 select(dst);
3404         }
3405         else if (updateSelection)
3406             select(branch);
3407         return true;
3408     }
3409     return false;
3410 }
3411
3412 bool VymModel::relinkImage(ImageItem *image, BranchItem *dst)
3413 {
3414     if (image && dst) {
3415         emit(layoutAboutToBeChanged());
3416
3417         BranchItem *pi = (BranchItem *)(image->parent());
3418         QString oldParString = getSelectString(pi);
3419         // Remove at current position
3420         int n = image->childNum();
3421         beginRemoveRows(index(pi), n, n);
3422         pi->removeChild(n);
3423         endRemoveRows();
3424
3425         // Add at dst
3426         QModelIndex dstix = index(dst);
3427         n = dst->getRowNumAppend(image);
3428         beginInsertRows(dstix, n, n);
3429         dst->appendChild(image);
3430         endInsertRows();
3431
3432         // Set new parent also for lmo
3433         if (image->getLMO() && dst->getLMO())
3434             image->getLMO()->setParObj(dst->getLMO());
3435
3436         emit(layoutChanged());
3437         saveState(image, QString("relinkTo (\"%1\")").arg(oldParString), image,
3438                   QString("relinkTo (\"%1\")").arg(getSelectString(dst)),
3439                   QString("Relink floatimage to %1").arg(getObjectName(dst)));
3440         return true;
3441     }
3442     return false;
3443 }
3444
3445 bool VymModel::relinkTo(const QString &dest, int num, QPointF pos)
3446 {
3447     TreeItem *selti = getSelectedItem();
3448     if (!selti)
3449         return false; // Nothing selected to relink
3450
3451     TreeItem *dst = findBySelectString(dest);
3452
3453     if (selti->isBranchLikeType()) {
3454         BranchItem *selbi = (BranchItem *)selti;
3455         if (!dst)
3456             return false; // Could not find destination
3457
3458         if (dst->getType() == TreeItem::Branch) {
3459             // Now try to relink to branch
3460             if (relinkBranch(selbi, (BranchItem *)dst, num, true)) {
3461                 emitSelectionChanged();
3462                 return true;
3463             }
3464             else
3465                 return false; // Relinking failed
3466         }
3467         else if (dst->getType() == TreeItem::MapCenter) {
3468             if (relinkBranch(selbi, (BranchItem *)dst, -1, true)) {
3469                 // Get coordinates of mainbranch
3470                 if (selbi->getLMO()) {
3471                     ((BranchObj *)selbi->getLMO())->move(pos);
3472                     ((BranchObj *)selbi->getLMO())->setRelPos();
3473                 }
3474                 reposition();
3475                 emitSelectionChanged();
3476                 return true;
3477             }
3478         }
3479         return false; // Relinking failed
3480     }
3481     else if (selti->getType() == TreeItem::Image) {
3482         if (dst->isBranchLikeType())
3483             if (relinkImage(((ImageItem *)selti), (BranchItem *)dst))
3484                 return true;
3485     }
3486     return false; // Relinking failed
3487 }
3488
3489 void VymModel::cleanupItems()
3490 {
3491     while (!deleteLaterIDs.isEmpty()) {
3492         TreeItem *ti = findID(deleteLaterIDs.takeFirst());
3493         if (ti)
3494             deleteItem(ti);
3495     }
3496 }
3497
3498 void VymModel::deleteLater(uint id)
3499 {
3500     if (!deleteLaterIDs.contains(id))
3501         deleteLaterIDs.append(id);
3502 }
3503
3504 void VymModel::deleteSelection()
3505 {
3506     QList<uint> selectedIDs = getSelectedIDs();
3507     unselectAll();
3508     QString fn;
3509
3510     foreach (uint id, selectedIDs) {
3511         TreeItem *ti = findID(id);
3512         if (ti) {
3513             if (ti->isBranchLikeType()) { // Delete branch
3514                 BranchItem *selbi = (BranchItem *)ti;
3515                 saveStateRemovingPart(
3516                     selbi, QString("remove %1").arg(getObjectName(selbi)));
3517
3518                 BranchItem *pi = (BranchItem *)(deleteItem(selbi));
3519                 if (pi) {
3520                     if (pi->isScrolled() && pi->branchCount() == 0)
3521                         pi->unScroll();
3522                     emitDataChanged(pi);
3523                     select(pi);
3524                 }
3525                 else
3526                     emitDataChanged(rootItem);
3527                 ti = NULL;
3528             }
3529             else {
3530                 // Delete other item
3531                 TreeItem *pi = ti->parent();
3532                 if (pi) {
3533                     if (ti->getType() == TreeItem::Image ||
3534                         ti->getType() == TreeItem::Attribute ||
3535                         ti->getType() == TreeItem::XLink) {
3536                         saveStateChangingPart(
3537                             pi, ti, "remove ()",
3538                             QString("Remove %1").arg(getObjectName(ti)));
3539
3540                         deleteItem(ti);
3541                         emitDataChanged(pi);
3542                         select(pi);
3543                         reposition();
3544                     }
3545                     else
3546                         qWarning(
3547                             "VymmModel::deleteSelection()  unknown type?!");
3548                 }
3549             }
3550         }
3551     }
3552 }
3553
3554 void VymModel::deleteKeepChildren(bool saveStateFlag)
3555 // deleteKeepChildren FIXME-3+ does not work yet for mapcenters
3556 // deleteKeepChildren FIXME-3+ children of scrolled branch stay invisible...
3557 {
3558     BranchItem *selbi = getSelectedBranch();
3559     BranchItem *pi;
3560     if (selbi) {
3561         // Don't use this on mapcenter
3562         if (selbi->depth() < 1)
3563             return;
3564
3565         pi = (BranchItem *)(selbi->parent());
3566         // Check if we have children at all to keep
3567         if (selbi->branchCount() == 0) {
3568             deleteSelection();
3569             return;
3570         }
3571
3572         QPointF p;
3573         if (selbi->getLMO())
3574             p = selbi->getLMO()->getRelPos();
3575         if (saveStateFlag)
3576             saveStateChangingPart(pi, pi, "removeKeepChildren ()",
3577                                   QString("Remove %1 and keep its children")
3578                                       .arg(getObjectName(selbi)));
3579
3580         QString sel = getSelectString(selbi);
3581         unselectAll();
3582         bool oldSaveState = saveStateBlocked;
3583         saveStateBlocked = true;
3584         int pos = selbi->num();
3585         BranchItem *bi = selbi->getFirstBranch();
3586         while (bi) {
3587             relinkBranch(bi, pi, pos, true);
3588             bi = selbi->getFirstBranch();
3589             pos++;
3590         }
3591         deleteItem(selbi);
3592         reposition();
3593         emitDataChanged(pi);
3594         select(sel);
3595         BranchObj *bo = getSelectedBranchObj();
3596         if (bo) {
3597             bo->move2RelPos(p);
3598             reposition();
3599         }
3600         saveStateBlocked = oldSaveState;
3601     }
3602 }
3603
3604 void VymModel::deleteChildren()
3605
3606 {
3607     BranchItem *selbi = getSelectedBranch();
3608     if (selbi) {
3609         saveStateChangingPart(
3610             selbi, selbi, "removeChildren ()",
3611             QString("Remove children of branch %1").arg(getObjectName(selbi)));
3612         emit(layoutAboutToBeChanged());
3613
3614         QModelIndex ix = index(selbi);
3615         int n = selbi->branchCount() - 1;
3616         beginRemoveRows(ix, 0, n);
3617         removeRows(0, n + 1, ix);
3618         endRemoveRows();
3619         if (selbi->isScrolled())
3620             unscrollBranch(selbi);
3621         emit(layoutChanged());
3622         reposition();
3623     }
3624 }
3625
3626 TreeItem *VymModel::deleteItem(TreeItem *ti)
3627 {
3628     if (ti) {
3629         TreeItem *pi = ti->parent();
3630         // qDebug()<<"VM::deleteItem  start ti="<<ti<<"  "<<ti->getHeading()<<"
3631         // pi="<<pi<<"="<<pi->getHeading();
3632
3633         TreeItem::Type t = ti->getType();
3634
3635         QModelIndex parentIndex = index(pi);
3636
3637         emit(layoutAboutToBeChanged());
3638
3639         int n = ti->childNum();
3640         beginRemoveRows(parentIndex, n, n);
3641         removeRows(n, 1, parentIndex);
3642         endRemoveRows();
3643
3644         // Size of parent branch might change when deleting images
3645         if (t == TreeItem::Image) {
3646             BranchObj *bo = (BranchObj *)(((BranchItem *)pi)->getMO());
3647             if (bo)
3648                 bo->calcBBoxSize();
3649         }
3650
3651         reposition();
3652
3653         emit(layoutChanged());
3654         emitUpdateQueries();
3655         if (!cleaningUpLinks)
3656             cleanupItems();
3657
3658         // qDebug()<<"VM::deleteItem  end   ti="<<ti;
3659         if (pi->depth() >= 0)
3660             return pi;
3661     }
3662     return NULL;
3663 }
3664
3665 void VymModel::deleteLink(Link *l)
3666 {
3667     if (xlinks.removeOne(l))
3668         delete (l);
3669 }
3670
3671 void VymModel::clearItem(TreeItem *ti)
3672 {
3673     if (ti) {
3674         // Clear task (or other data in item itself)
3675         ti->clear();
3676
3677         QModelIndex parentIndex = index(ti);
3678         if (!parentIndex.isValid())
3679             return;
3680
3681         int n = ti->childCount();
3682         if (n == 0)
3683             return;
3684
3685         emit(layoutAboutToBeChanged());
3686
3687         beginRemoveRows(parentIndex, 0, n - 1);
3688         removeRows(0, n, parentIndex);
3689         endRemoveRows();
3690
3691         reposition();
3692
3693         emit(layoutChanged());
3694     }
3695     return;
3696 }
3697
3698 bool VymModel::scrollBranch(BranchItem *bi)
3699 {
3700     if (bi) {
3701         if (bi->isScrolled())
3702             return false;
3703         if (bi->branchCount() == 0)
3704             return false;
3705         if (bi->depth() == 0)
3706             return false;
3707         if (bi->toggleScroll()) {
3708             QString u, r;
3709             r = "scroll";
3710             u = "unscroll";
3711             saveState(bi, QString("%1 ()").arg(u), bi, QString("%1 ()").arg(r),
3712                       QString("%1 %2").arg(r).arg(getObjectName(bi)));
3713             emitDataChanged(bi);
3714             emitSelectionChanged();
3715             reposition();
3716             mapEditor->getScene()
3717                 ->update(); // Needed for _quick_ update,  even in 1.13.x
3718             return true;
3719         }
3720     }
3721     return false;
3722 }
3723
3724 bool VymModel::unscrollBranch(BranchItem *bi)
3725 {
3726     if (bi) {
3727         if (!bi->isScrolled())
3728             return false;
3729         if (bi->toggleScroll()) {
3730             QString u, r;
3731             u = "scroll";
3732             r = "unscroll";
3733             saveState(bi, QString("%1 ()").arg(u), bi, QString("%1 ()").arg(r),
3734                       QString("%1 %2").arg(r).arg(getObjectName(bi)));
3735             emitDataChanged(bi);
3736             emitSelectionChanged();
3737             reposition();
3738             mapEditor->getScene()
3739                 ->update(); // Needed for _quick_ update,  even in 1.13.x
3740             return true;
3741         }
3742     }
3743     return false;
3744 }
3745
3746 void VymModel::toggleScroll()
3747 {
3748     BranchItem *selbi = getSelectedBranch();
3749     if (selbi) {
3750         if (selbi->isScrolled())
3751             unscrollBranch(selbi);
3752         else
3753             scrollBranch(selbi);
3754         // Note: saveState & reposition are called in above functions
3755     }
3756 }
3757
3758 void VymModel::unscrollChildren()
3759 {
3760     BranchItem *selbi = getSelectedBranch();
3761     if (selbi) {
3762         saveStateChangingPart(
3763             selbi, selbi, QString("unscrollChildren ()"),
3764             QString("unscroll all children of %1").arg(getObjectName(selbi)));
3765         BranchItem *prev = NULL;
3766         BranchItem *cur = NULL;
3767         nextBranch(cur, prev, true, selbi);
3768         while (cur) {
3769             if (cur->isScrolled()) {
3770                 cur->toggleScroll();
3771                 emitDataChanged(cur);
3772             }
3773             nextBranch(cur, prev, true, selbi);
3774         }
3775         updateActions();
3776         reposition();
3777         // Would this help??? emitSelectionChanged();
3778     }
3779 }
3780
3781 void VymModel::setScaleFactor(qreal f, ImageItem *selii)
3782 {
3783     if (!selii)
3784         selii = getSelectedImage();
3785
3786     if (selii) {
3787         qreal f_old = selii->getScaleFactor();
3788         selii->setScaleFactor(f);
3789         saveState(selii, QString("setScaleFactor(%1)").arg(f_old), selii,
3790                   QString("setScaleFactor(%1)").arg(f),
3791                   QString("Scale %1").arg(getObjectName(selii)));
3792         reposition();
3793     }
3794 }
3795
3796 void VymModel::growSelectionSize() // FIXME-3 Also for heading in BranchItem?
3797 {
3798     ImageItem *selii = getSelectedImage();
3799     if (selii) {
3800         qreal f = 0.05;
3801         qreal sx = selii->getScaleFactor();
3802         setScaleFactor(sx + f);
3803     }
3804 }
3805
3806 void VymModel::shrinkSelectionSize()
3807 {
3808     ImageItem *selii = getSelectedImage();
3809     if (selii) {
3810         qreal f = 0.05;
3811         qreal sx = selii->getScaleFactor();
3812         setScaleFactor(sx - f);
3813     }
3814 }
3815
3816 void VymModel::resetSelectionSize()
3817 {
3818     ImageItem *selii = getSelectedImage();
3819     if (selii)
3820         setScaleFactor(1);
3821 }
3822
3823 void VymModel::emitExpandAll() { emit(expandAll()); }
3824
3825 void VymModel::emitExpandOneLevel() { emit(expandOneLevel()); }
3826
3827 void VymModel::emitCollapseOneLevel() { emit(collapseOneLevel()); }
3828
3829 void VymModel::emitCollapseUnselected() { emit(collapseUnselected()); }
3830
3831 void VymModel::toggleTarget()
3832 {
3833     foreach (TreeItem *ti, getSelectedItems()) {
3834         if (ti->isBranchLikeType()) {
3835             ((BranchItem*)ti)->toggleTarget();
3836             saveState(ti, "toggleTarget()", ti, "toggleTarget()",
3837                       "Toggle target");
3838         }
3839     }
3840     reposition();
3841 }
3842
3843 ItemList VymModel::getLinkedMaps()
3844 {
3845     ItemList targets;
3846
3847     // rmodel->setSearchString (s);
3848
3849     BranchItem *cur = NULL;
3850     BranchItem *prev = NULL;
3851     nextBranch(cur, prev);
3852
3853     QString s;
3854
3855     while (cur) {
3856         if (cur->hasActiveSystemFlag("system-target") &&
3857             !cur->getVymLink().isEmpty()) {
3858             s = cur->getHeading().getTextASCII();
3859             s.replace(QRegularExpression("\n+"), " ");
3860             s.replace(QRegularExpression("\\s+"), " ");
3861             s.replace(QRegularExpression("^\\s+"), "");
3862
3863             QStringList sl;
3864             sl << s;
3865             sl << cur->getVymLink();
3866
3867             targets[cur->getID()] = sl;
3868         }
3869         nextBranch(cur, prev);
3870     }
3871     return targets;
3872 }
3873
3874 ItemList VymModel::getTargets()
3875 {
3876     ItemList targets;
3877
3878     // rmodel->setSearchString (s);
3879
3880     BranchItem *cur = NULL;
3881     BranchItem *prev = NULL;
3882     nextBranch(cur, prev);
3883
3884     QString s;
3885
3886     while (cur) {
3887         if (cur->hasActiveSystemFlag("system-target")) {
3888             s = cur->getHeading().getTextASCII();
3889             s.replace(QRegularExpression("\n+"), " ");
3890             s.replace(QRegularExpression("\\s+"), " ");
3891             s.replace(QRegularExpression("^\\s+"), "");
3892
3893             QStringList sl;
3894             sl << s;
3895
3896             targets[cur->getID()] = sl;
3897         }
3898         nextBranch(cur, prev);
3899     }
3900     return targets;
3901 }
3902
3903 Flag* VymModel::findFlagByName(const QString &name)
3904 {
3905     BranchItem *bi = getSelectedBranch();
3906
3907     if (bi) {
3908         Flag *f = standardFlagsMaster->findFlagByName(name);
3909         if (!f) {
3910             f = userFlagsMaster->findFlagByName(name);
3911             if (!f) {
3912                 qWarning() << "VymModel::findFlagByName failed for flag named "
3913                            << name;
3914                 return nullptr;
3915             }
3916         }
3917         return f;
3918     }
3919
3920     // Nothing selected, so no flag found
3921     return nullptr;
3922 }
3923
3924 void VymModel::setFlagByName(const QString &name, bool useGroups)
3925 {
3926     BranchItem *bi = getSelectedBranch();
3927
3928     if (bi && !bi->hasActiveFlag(name)) {
3929         toggleFlagByName(name, useGroups);
3930     }
3931 }
3932
3933 void VymModel::unsetFlagByName(const QString &name)
3934 {
3935     BranchItem *bi = getSelectedBranch();
3936
3937     if (bi && bi->hasActiveFlag(name)) {
3938         toggleFlagByName(name);
3939     }
3940 }
3941
3942 void VymModel::toggleFlagByName(const QString &name, bool useGroups)
3943 {
3944     BranchItem *bi = getSelectedBranch();
3945
3946     if (bi) {
3947         Flag *f = findFlagByName(name);
3948
3949         if (!f) {
3950             qWarning() << "VymModel::toggleFlagByName could not find flag named " << name;
3951             return;
3952         }
3953
3954         toggleFlagByUid(f->getUuid(), useGroups);
3955     }
3956 }
3957
3958 void VymModel::toggleFlagByUid(
3959     const QUuid &uid,
3960     bool useGroups)
3961     // FIXME-2  saveState not correct when toggling flags in groups
3962     // (previous flags not saved!)
3963 {
3964     QStringList itemList = getSelectedUUIDs();
3965
3966     if (itemList.count() > 0) {
3967         QString fn;
3968         TreeItem *ti;
3969         BranchItem *bi;
3970         Flag *f;
3971         foreach (QString id, itemList) {
3972             ti = findUuid(QUuid(id));
3973             if (ti && ti->isBranchLikeType()) {
3974                     bi = (BranchItem*)ti;
3975                 f = bi->toggleFlagByUid(uid, useGroups);
3976
3977                 if (f) {
3978                     QString u = "toggleFlagByUid";
3979                     QString name = f->getName();
3980                     saveState(bi, QString("%1 (\"%2\")").arg(u).arg(uid.toString()), bi,
3981                               QString("%1 (\"%2\")").arg(u).arg(uid.toString()),
3982                               QString("Toggling flag \"%1\" of %2")
3983                                   .arg(name)
3984                                   .arg(getObjectName(bi)));
3985                     emitDataChanged(bi);
3986                 } else
3987                     qWarning() << "VymModel::toggleFlag failed for flag with uid "
3988                                << uid;
3989             }
3990         }
3991         reposition();
3992     }
3993 }
3994
3995
3996 void VymModel::clearFlags()
3997 {
3998     BranchItem *selbi = getSelectedBranch();
3999     if (selbi) {
4000         selbi->deactivateAllStandardFlags();
4001         reposition();
4002         emitDataChanged(selbi);
4003         setChanged();
4004     }
4005 }
4006
4007 void VymModel::colorBranch(QColor c)
4008 {
4009     QList<BranchItem *> selbis = getSelectedBranches();
4010     foreach (BranchItem *selbi, selbis) {
4011         saveState(selbi,
4012                   QString("colorBranch (\"%1\")")
4013                       .arg(selbi->getHeadingColor().name()),
4014                   selbi, QString("colorBranch (\"%1\")").arg(c.name()),
4015                   QString("Set color of %1 to %2")
4016                       .arg(getObjectName(selbi))
4017                       .arg(c.name()));
4018         selbi->setHeadingColor(c); // color branch
4019         emitDataChanged(selbi);
4020         taskEditor->showSelection();
4021     }
4022     mapEditor->getScene()->update();
4023 }
4024
4025 void VymModel::colorSubtree(QColor c, BranchItem *b)
4026 {
4027     QList<BranchItem *> selbis;
4028     if (b)
4029         selbis.append(b);
4030     else
4031         selbis = getSelectedBranches();
4032
4033     foreach (BranchItem *bi, selbis) {
4034         saveStateChangingPart(bi, bi,
4035                               QString("colorSubtree (\"%1\")").arg(c.name()),
4036                               QString("Set color of %1 and children to %2")
4037                                   .arg(getObjectName(bi))
4038                                   .arg(c.name()));
4039         BranchItem *prev = NULL;
4040         BranchItem *cur = NULL;
4041         nextBranch(cur, prev, true, bi);
4042         while (cur) {
4043             cur->setHeadingColor(c); // color links, color children
4044             emitDataChanged(cur);
4045             nextBranch(cur, prev, true, bi);
4046         }
4047     }
4048     taskEditor->showSelection();
4049     mapEditor->getScene()->update();
4050 }
4051
4052 QColor VymModel::getCurrentHeadingColor()
4053 {
4054     BranchItem *selbi = getSelectedBranch();
4055     if (selbi)
4056         return selbi->getHeadingColor();
4057
4058     QMessageBox::warning(
4059         0, "Warning",
4060         "Can't get color of heading,\nthere's no branch selected");
4061     return Qt::black;
4062 }
4063
4064 void VymModel::note2URLs()
4065 {
4066     BranchItem *selbi = getSelectedBranch();
4067     if (selbi) {
4068         saveStateChangingPart(
4069             selbi, selbi, QString("note2URLs()"),
4070             QString("Extract URLs from note of %1").arg(getObjectName(selbi)));
4071
4072         QString n = selbi->getNoteASCII();
4073         if (n.isEmpty())
4074             return;
4075         QRegExp re("(http.*)(\\s|\"|')");
4076         re.setMinimal(true);
4077
4078         BranchItem *bi;
4079         int pos = 0;
4080         while ((pos = re.indexIn(n, pos)) != -1) {
4081             bi = createBranch(selbi);
4082             bi->setHeadingPlainText(re.cap(1));
4083             bi->setURL(re.cap(1));
4084             emitDataChanged(bi);
4085             pos += re.matchedLength();
4086         }
4087     }
4088 }
4089
4090 void VymModel::editHeading2URL()
4091 {
4092     TreeItem *selti = getSelectedItem();
4093     if (selti)
4094         setURL(selti->getHeadingPlain());
4095 }
4096
4097 void VymModel::getJiraData(bool subtree) // FIXME-2 update error message, check
4098                                          // if jiraClientAvail is set correctly
4099 {
4100     if (!JiraAgent::available()) {
4101         WarningDialog dia;
4102         QString w = QObject::tr("JIRA agent not setup.");
4103         dia.setText(w);
4104         dia.setWindowTitle( tr("Warning") + ": " + w);
4105         dia.setShowAgainName("/JiraAgent/notdefined");
4106         dia.exec();
4107
4108         if (!mainWindow->settingsJIRA())
4109             return;
4110     }
4111
4112     BranchItem *selbi = getSelectedBranch();
4113     QRegExp re("(\\w+[-|\\s]\\d+)");
4114
4115     if (selbi) {
4116         QString url;
4117         BranchItem *prev = nullptr;
4118         BranchItem *cur = nullptr;
4119         nextBranch(cur, prev, true, selbi);
4120         while (cur) {
4121             QString heading = cur->getHeadingPlain();
4122
4123             if (re.indexIn(heading) >= 0) {
4124                 // Create agent
4125                 JiraAgent *agent = new JiraAgent;
4126                 agent->setJobType(JiraAgent::GetTicketInfo);
4127                 if (!agent->setBranch(cur)) {
4128                     qWarning () << "Could not set branch in JiraAgent to " << cur->getHeadingPlain();
4129                     delete agent;
4130                     return;
4131                 }
4132                 if (!agent->setTicket(heading)) {
4133                     mainWindow->statusMessage(tr("Could not find Jira ticket pattern in %1", "VymModel").arg(cur->getHeadingPlain()));
4134                     delete agent;
4135                     return;
4136                 }
4137
4138                 //setURL(agent->url(), false, cur);
4139
4140                 connect(agent, &JiraAgent::jiraTicketReady, this, &VymModel::updateJiraData);
4141
4142                 // Start contacting JIRA in background
4143                 agent->startJob();
4144                 mainWindow->statusMessage(tr("Contacting Jira...", "VymModel"));
4145             }
4146
4147
4148             if (subtree)
4149                 nextBranch(cur, prev, true, selbi);
4150             else
4151                 cur = nullptr;
4152         }
4153     }
4154 }
4155
4156 void VymModel::updateJiraData(QJsonObject jsobj)
4157 {
4158     QJsonDocument jsdoc = QJsonDocument (jsobj);
4159     QString key = jsobj["key"].toString();
4160     QJsonObject fields = jsobj["fields"].toObject();
4161
4162     QJsonObject assigneeObj = fields["assignee"].toObject();
4163     QString assignee = assigneeObj["emailAddress"].toString();
4164
4165     QJsonObject reporterObj = fields["reporter"].toObject();
4166     QString reporter  = reporterObj["emailAddress"].toString();
4167
4168     QJsonObject resolutionObj = fields["resolution"].toObject();
4169     QString resolution  = resolutionObj["name"].toString();
4170
4171     QJsonObject statusObj = fields["status"].toObject();
4172     QString status  = statusObj["name"].toString();
4173
4174     QString summary = fields["summary"].toString();
4175
4176     QJsonArray componentsArray = fields["components"].toArray();
4177     QJsonObject compObj;
4178     QString components;
4179     for (int i = 0; i < componentsArray.size(); ++i) {
4180         compObj = componentsArray[i].toObject();
4181         components += compObj["name"].toString();
4182     }
4183
4184     int branchID = jsobj["vymBranchId"].toInt();
4185
4186     QStringList solvedStates;
4187     solvedStates << "Verification Done";
4188     solvedStates << "Resolved";
4189     solvedStates << "Closed";
4190
4191     QString keyName = key;
4192     BranchItem *bi = (BranchItem*)findID(branchID);
4193     if (bi) {
4194         if (solvedStates.contains(status))    {
4195             keyName = "(" + keyName + ")";
4196             colorSubtree (Qt::blue, bi);
4197         }
4198
4199         setHeadingPlainText(keyName + ": " + summary, bi);
4200         setURL(jsobj["vymTicketUrl"].toString());
4201
4202         AttributeItem *ai;
4203
4204         ai = new AttributeItem("JIRA.assignee", assignee);
4205         setAttribute(bi, ai);
4206
4207         ai = new AttributeItem("JIRA.reporter", reporter);
4208         setAttribute(bi, ai);
4209
4210         ai = new AttributeItem("JIRA.resolution", resolution);
4211         setAttribute(bi, ai);
4212
4213         ai = new AttributeItem("JIRA.status", status);
4214         setAttribute(bi, ai);
4215
4216         ai = new AttributeItem("JIRA.components", components);
4217         setAttribute(bi, ai);
4218     }
4219
4220     /* Pretty print JIRA ticket
4221     vout << jsdoc.toJson(QJsonDocument::Indented) << endl;
4222     vout << "       Key: " + key << endl;
4223     vout << "      Desc: " + summary << endl;
4224     vout << "  Assignee: " + assignee << endl;
4225     vout << "Components: " + components << endl;
4226     vout << "  Reporter: " + reporter << endl;
4227     vout << "Resolution: " + resolution << endl;
4228     vout << "    Status: " + status << endl;
4229     */
4230
4231     mainWindow->statusMessage(tr("Received Jira data.", "VymModel"));
4232 }
4233
4234
4235 void VymModel::setHeadingConfluencePageName()   // FIXME-2 always asks for Confluence credentials when adding any URL
4236 {
4237     BranchItem *selbi = getSelectedBranch();
4238     if (selbi) {
4239         QString url = selbi->getURL();
4240         if (!url.isEmpty() && 
4241                 settings.contains("/atlassian/confluence/url") &&
4242                 url.contains(settings.value("/atlassian/confluence/url").toString())) {
4243
4244             ConfluenceAgent *ca_setHeading = new ConfluenceAgent(selbi);
4245             ca_setHeading->setPageURL(url);
4246             ca_setHeading->setJobType(ConfluenceAgent::CopyPagenameToHeading);
4247             ca_setHeading->startJob();
4248         }
4249     }
4250 }
4251
4252 void VymModel::setVymLink(const QString &s)
4253 {
4254     if (s.isEmpty()) return;
4255
4256     BranchItem *bi = getSelectedBranch();
4257     if (bi) {
4258         saveState(
4259             bi, "setVymLink (\"" + bi->getVymLink() + "\")", bi,
4260             "setVymLink (\"" + s + "\")",
4261             QString("Set vymlink of %1 to %2").arg(getObjectName(bi)).arg(s));
4262         bi->setVymLink(s);
4263         emitDataChanged(bi);
4264         reposition();
4265     }
4266 }
4267
4268 void VymModel::deleteVymLink()
4269 {
4270     BranchItem *bi = getSelectedBranch();
4271     if (bi) {
4272         saveState(bi, "setVymLink (\"" + bi->getVymLink() + "\")", bi,
4273                   "setVymLink (\"\")",
4274                   QString("Unset vymlink of %1").arg(getObjectName(bi)));
4275         bi->setVymLink("");
4276         emitDataChanged(bi);
4277         reposition();
4278         updateActions();
4279     }
4280 }
4281
4282 QString VymModel::getVymLink()
4283 {
4284     BranchItem *bi = getSelectedBranch();
4285     if (bi)
4286         return bi->getVymLink();
4287     else
4288         return "";
4289 }
4290
4291 QStringList VymModel::getVymLinks()
4292 {
4293     QStringList links;
4294     BranchItem *selbi = getSelectedBranch();
4295     BranchItem *cur = NULL;
4296     BranchItem *prev = NULL;
4297     nextBranch(cur, prev, true, selbi);
4298     while (cur) {
4299         if (!cur->getVymLink().isEmpty())
4300             links.append(cur->getVymLink());
4301         nextBranch(cur, prev, true, selbi);
4302     }
4303     return links;
4304 }
4305
4306 void VymModel::followXLink(int i)
4307 {
4308     BranchItem *selbi = getSelectedBranch();
4309     if (selbi) {
4310         selbi = selbi->getXLinkItemNum(i)->getPartnerBranch();
4311         if (selbi)
4312             select(selbi);
4313     }
4314 }
4315
4316 void VymModel::editXLink()
4317 {
4318     Link *l = getSelectedXLink();
4319     if (l) {
4320         EditXLinkDialog dia;
4321         dia.setLink(l);
4322         if (dia.exec() == QDialog::Accepted) {
4323             if (dia.useSettingsGlobal()) {
4324                 setMapDefXLinkPen(l->getPen());
4325                 setMapDefXLinkStyleBegin(l->getStyleBeginString());
4326                 setMapDefXLinkStyleEnd(l->getStyleEndString());
4327             }
4328         }
4329     }
4330 }
4331
4332 void VymModel::setXLinkColor(const QString &new_col)
4333 {
4334     Link *l = getSelectedXLink();
4335     if (l) {
4336         QPen pen = l->getPen();
4337         QColor new_color = QColor(new_col);
4338         QColor old_color = pen.color();
4339         if (new_color == old_color)
4340             return;
4341         pen.setColor(new_color);
4342         l->setPen(pen);
4343         saveState(l->getBeginLinkItem(),
4344                   QString("setXLinkColor(\"%1\")").arg(old_color.name()),
4345                   l->getBeginLinkItem(),
4346                   QString("setXLinkColor(\"%1\")").arg(new_color.name()),
4347                   QString("set color of xlink to %1").arg(new_color.name()));
4348     }
4349 }
4350
4351 void VymModel::setXLinkStyle(const QString &new_style)
4352 {
4353     Link *l = getSelectedXLink();
4354     if (l) {
4355         QPen pen = l->getPen();
4356         QString old_style = penStyleToString(pen.style());
4357         if (new_style == old_style)
4358             return;
4359         bool ok;
4360         pen.setStyle(penStyle(new_style, ok));
4361         l->setPen(pen);
4362         saveState(l->getBeginLinkItem(),
4363                   QString("setXLinkStyle(\"%1\")").arg(old_style),
4364                   l->getBeginLinkItem(),
4365                   QString("setXLinkStyle(\"%1\")").arg(new_style),
4366                   QString("set style of xlink to %1").arg(new_style));
4367     }
4368 }
4369
4370 void VymModel::setXLinkStyleBegin(const QString &new_style)
4371 {
4372     Link *l = getSelectedXLink();
4373     if (l) {
4374         QString old_style = l->getStyleBeginString();
4375         if (new_style == old_style)
4376             return;
4377         l->setStyleBegin(new_style);
4378         saveState(l->getBeginLinkItem(),
4379                   QString("setXLinkStyleBegin(\"%1\")").arg(old_style),
4380                   l->getBeginLinkItem(),
4381                   QString("setXLinkStyleBegin(\"%1\")").arg(new_style),
4382                   "set style of xlink begin");
4383     }
4384 }
4385
4386 void VymModel::setXLinkStyleEnd(const QString &new_style)
4387 {
4388     Link *l = getSelectedXLink();
4389     if (l) {
4390         QString old_style = l->getStyleEndString();
4391         if (new_style == old_style)
4392             return;
4393         l->setStyleEnd(new_style);
4394         saveState(l->getBeginLinkItem(),
4395                   QString("setXLinkStyleEnd(\"%1\")").arg(old_style),
4396                   l->getBeginLinkItem(),
4397                   QString("setXLinkStyleEnd(\"%1\")").arg(new_style),
4398                   "set style of xlink end");
4399     }
4400 }
4401
4402 void VymModel::setXLinkWidth(int new_width)
4403 {
4404     Link *l = getSelectedXLink();
4405     if (l) {
4406         QPen pen = l->getPen();
4407         int old_width = pen.width();
4408         if (new_width == old_width)
4409             return;
4410         pen.setWidth(new_width);
4411         l->setPen(pen);
4412         saveState(
4413             l->getBeginLinkItem(), QString("setXLinkWidth(%1)").arg(old_width),
4414             l->getBeginLinkItem(), QString("setXLinkWidth(%1)").arg(new_width),
4415             "set width of xlink");
4416     }
4417 }
4418
4419 //////////////////////////////////////////////
4420 // Scripting
4421 //////////////////////////////////////////////
4422
4423 QVariant VymModel::execute(
4424     const QString &script) // FIXME-3 still required???
4425                            // Called from these places:
4426                            //
4427                            // scripts/vym-ruby.rb  (and adaptormodel) used for
4428                            // testing Main::callMacro Main::checkReleaseNotes
4429                            // VymModel::undo
4430                            // VymModel::redo
4431                            // VymModel::exportLast
4432                            // VymModel::updateSlideSelection
4433 {
4434     // qDebug()<<"VM::execute called: "<<script;
4435     return mainWindow->runScript(script);
4436 }
4437
4438 void VymModel::setExportMode(bool b)
4439 {
4440     // should be called before and after exports
4441     // depending on the settings
4442     if (b && settings.value("/export/useHideExport", "true") == "true")
4443         setHideTmpMode(TreeItem::HideExport);
4444     else
4445         setHideTmpMode(TreeItem::HideNone);
4446 }
4447
4448 QPointF VymModel::exportImage(QString fname, bool askName, QString format)
4449 {
4450     QPointF offset; // set later, when getting image from MapEditor
4451
4452     if (fname == "") {
4453         if (!askName) {
4454             qWarning("VymModel::exportImage called without filename (and "
4455                      "askName==false)");
4456             return offset;
4457         }
4458
4459         fname = lastImageDir.absolutePath() + "/" + getMapName() + ".png";
4460         format = "PNG";
4461     }
4462
4463     ExportBase ex;
4464     ex.setName("Image");
4465     ex.setModel(this);
4466     ex.setFilePath(fname);
4467     ex.setWindowTitle(tr("Export map as image"));
4468     ex.addFilter(
4469         "PNG (*.png);;All (* *.*)"); //  imageIO.getFilters().join(";;")
4470     ex.setLastCommand(
4471         settings.localValue(filePath, "/export/last/command", "").toString());
4472
4473     if (askName) {
4474         if (!ex.execDialog())
4475             return offset;
4476         fname = ex.getFilePath();
4477         lastImageDir = QDir(fname);
4478     }
4479
4480     setExportMode(true);
4481
4482     mapEditor->minimizeView();
4483
4484     QImage img(mapEditor->getImage(offset));
4485     if (!img.save(fname, format.toLocal8Bit())) {
4486         QMessageBox::critical(
4487             0, tr("Critical Error"),
4488             tr("Couldn't save QImage %1 in format %2").arg(fname).arg(format));
4489         ex.setResult(ExportBase::Failed);
4490     } else
4491         ex.setResult(ExportBase::Success);
4492
4493     setExportMode(false);
4494
4495     ex.completeExport();
4496
4497     return offset;
4498 }
4499
4500 void VymModel::exportPDF(QString fname, bool askName)
4501 {
4502     if (fname == "") {
4503         if (!askName) {
4504             qWarning("VymModel::exportPDF called without filename (and "
4505                      "askName==false)");
4506             return;
4507         }
4508
4509         fname = lastExportDir.absolutePath() + "/" + getMapName() + ".pdf";
4510     }
4511
4512     ExportBase ex;
4513     ex.setName("PDF");
4514     ex.setModel(this);
4515     ex.setFilePath(fname);
4516     ex.setWindowTitle(tr("Export map as PDF"));
4517     ex.addFilter("PDF (*.pdf);;All (* *.*)");
4518     ex.setLastCommand(
4519         settings.localValue(filePath, "/export/last/command", "").toString());
4520
4521     if (askName) {
4522         if (!ex.execDialog())
4523             return;
4524         fname = ex.getFilePath();
4525     }
4526
4527     setExportMode(true);
4528
4529     // To PDF
4530     QPrinter pdfPrinter(QPrinter::HighResolution);
4531     pdfPrinter.setOutputFormat(QPrinter::PdfFormat);
4532     pdfPrinter.setOutputFileName(fname);
4533     pdfPrinter.setPageSize(QPageSize(QPageSize::A3));
4534
4535     QRectF bbox = mapEditor->getTotalBBox();
4536     if (bbox.width() > bbox.height())
4537         // recommend landscape
4538         pdfPrinter.setPageOrientation(QPageLayout::Landscape);
4539     else
4540         // recommend portrait
4541         pdfPrinter.setPageOrientation(QPageLayout::Portrait);
4542
4543     QPainter *pdfPainter = new QPainter(&pdfPrinter);
4544     getScene()->render(pdfPainter);
4545     pdfPainter->end();
4546     delete pdfPainter;
4547
4548     setExportMode(false);
4549
4550     ex.completeExport();
4551 }
4552
4553 QPointF VymModel::exportSVG(QString fname, bool askName)
4554 {
4555     QPointF offset; // FIXME-3 not needed?
4556
4557     if (fname == "") {
4558         if (!askName) {
4559             qWarning("VymModel::exportSVG called without filename (and "
4560                      "askName==false)");
4561             return offset;
4562         }
4563
4564         fname = lastImageDir.absolutePath() + "/" + getMapName() + ".svg";
4565     }
4566
4567     ExportBase ex;
4568     ex.setName("SVG");
4569     ex.setModel(this);
4570     ex.setFilePath(fname);
4571     ex.setWindowTitle(tr("Export map as SVG"));
4572     ex.addFilter("SVG (*.svg);;All (* *.*)");
4573
4574     if (askName) {
4575         if (!ex.execDialog())
4576             return offset;
4577         fname = ex.getFilePath();
4578         lastImageDir = QDir(fname);
4579     }
4580
4581     setExportMode(true);
4582
4583     QSvgGenerator generator;
4584     generator.setFileName(fname);
4585     QSize sceneSize = getScene()->sceneRect().size().toSize();
4586     generator.setSize(sceneSize);
4587     generator.setViewBox(QRect(0, 0, sceneSize.width(), sceneSize.height()));
4588     QPainter *svgPainter = new QPainter(&generator);
4589     getScene()->render(svgPainter);
4590     svgPainter->end();
4591     delete svgPainter;
4592
4593     setExportMode(false);
4594     ex.completeExport();
4595
4596     return offset;
4597 }
4598
4599 void VymModel::exportXML(QString fpath, QString dpath, bool useDialog)
4600 {
4601     ExportBase ex;
4602     ex.setName("XML");
4603     ex.setModel(this);
4604     ex.setWindowTitle(tr("Export map as XML"));
4605     ex.addFilter("XML (*.xml);;All (* *.*)");
4606     ex.setLastCommand(
4607         settings.localValue(filePath, "/export/last/command", "").toString());
4608
4609     if (useDialog) {
4610         QFileDialog fd;
4611         fd.setWindowTitle(vymName + " - " + tr("Export XML to directory"));
4612         QStringList filters;
4613         filters << "XML data (*.xml)";
4614         fd.setNameFilters(filters);
4615         fd.setOption(QFileDialog::DontConfirmOverwrite, true);
4616         fd.setAcceptMode(QFileDialog::AcceptSave);
4617         fd.selectFile(mapName + ".xml");
4618
4619         QString fn;
4620         if (fd.exec() != QDialog::Accepted || fd.selectedFiles().isEmpty())
4621             return;
4622
4623         fpath = fd.selectedFiles().first();
4624         dpath = fpath.left(fpath.lastIndexOf("/"));
4625
4626         if (!confirmDirectoryOverwrite(QDir(dpath)))
4627             return;
4628     }
4629     ex.setFilePath(fpath);
4630
4631     QString mname = basename(fpath);
4632
4633     // Hide stuff during export, if settings want this
4634     setExportMode(true);
4635
4636     // Create subdirectories
4637     makeSubDirs(dpath);
4638
4639     // write image and calculate offset (Remember old mapSaved setting while
4640     // exporting image)
4641     bool mchanged = mapChanged;
4642     bool munsaved = mapUnsaved;
4643
4644     QPointF offset =
4645         exportImage(dpath + "/images/" + mname + ".png", false, "PNG");
4646
4647     mapChanged = mchanged;
4648     mapUnsaved = munsaved;
4649
4650     // write to directory   //FIXME-3 check totalBBox here...
4651     QString saveFile =
4652         saveToDir(dpath, mname + "-", FlagRowMaster::NoFlags, offset, NULL);
4653     QFile file;
4654
4655     file.setFileName(fpath);
4656     if (!file.open(QIODevice::WriteOnly)) {
4657         // This should neverever happen
4658         QMessageBox::critical(0, tr("Critical Export Error"),
4659                               QString("VymModel::exportXML couldn't open %1")
4660                                   .arg(file.fileName()));
4661         return;
4662     }
4663
4664     // Write it finally, and write in UTF8, no matter what
4665     QTextStream ts(&file);
4666     ts.setCodec("UTF-8");
4667     ts << saveFile;
4668     file.close();
4669
4670     setExportMode(false);
4671
4672     QStringList args;
4673     args << fpath;
4674     args << dpath;
4675     ex.completeExport(args);
4676 }
4677
4678 void VymModel::exportAO(QString fname, bool askName)
4679 {
4680     ExportAO ex;
4681     ex.setModel(this);
4682     ex.setLastCommand(
4683         settings.localValue(filePath, "/export/last/command", "").toString());
4684
4685     if (fname == "")
4686         ex.setFilePath(mapName + ".txt");
4687     else
4688         ex.setFilePath(fname);
4689
4690     if (askName) {
4691         ex.setDirPath(lastExportDir.absolutePath());
4692         ex.execDialog();
4693     }
4694     if (!ex.canceled()) {
4695         setExportMode(true);
4696         ex.doExport();
4697         setExportMode(false);
4698     }
4699 }
4700
4701 void VymModel::exportASCII(const QString &fname, bool listTasks, bool askName)
4702 {
4703     ExportASCII ex;
4704     ex.setModel(this);
4705     ex.setListTasks(listTasks);
4706     ex.setLastCommand(
4707         settings.localValue(filePath, "/export/last/command", "").toString());
4708
4709     if (fname == "")
4710         ex.setFilePath(mapName + ".txt");
4711     else
4712         ex.setFilePath(fname);
4713
4714     if (askName) {
4715         ex.setDirPath(lastExportDir.absolutePath());
4716         ex.execDialog();
4717     }
4718
4719     if (!ex.canceled()) {
4720         setExportMode(true);
4721         ex.doExport();
4722         setExportMode(false);
4723     }
4724 }
4725
4726 void VymModel::exportCSV(const QString &fname, bool askName)
4727 {
4728     ExportCSV ex;
4729     ex.setModel(this);
4730     ex.setLastCommand(
4731         settings.localValue(filePath, "/export/last/command", "").toString());
4732
4733     if (fname == "")
4734         ex.setFilePath(mapName + ".csv");
4735     else
4736         ex.setFilePath(fname);
4737
4738     if (askName) {
4739         ex.addFilter("CSV (*.csv);;All (* *.*)");
4740         ex.setDirPath(lastExportDir.absolutePath());
4741         ex.setWindowTitle(vymName + " -" + tr("Export as csv") + " " +
4742                           tr("(still experimental)"));
4743         ex.execDialog();
4744     }
4745
4746     if (!ex.canceled()) {
4747         setExportMode(true);
4748         ex.doExport();
4749         setExportMode(false);
4750     }
4751 }
4752
4753 void VymModel::exportFirefoxBookmarks(const QString &fname, bool askName)
4754 {
4755     ExportFirefox ex;
4756     ex.setModel(this);
4757     ex.setLastCommand(
4758         settings.localValue(filePath, "/export/last/command", "").toString());
4759
4760     if (fname == "")
4761         ex.setFilePath(mapName + ".csv");
4762     else
4763         ex.setFilePath(fname);
4764
4765     if (askName) {
4766         ex.addFilter("JSON (*.json);;All (* *.*)");
4767         ex.setDirPath(lastExportDir.absolutePath());
4768         ex.setWindowTitle(vymName + " -" + tr("Export as csv") + " " +
4769                           tr("(still experimental)"));
4770         ex.execDialog();
4771     }
4772
4773     if (!ex.canceled()) {
4774         setExportMode(true);
4775         ex.doExport();
4776         setExportMode(false);
4777     }
4778 }
4779
4780 void VymModel::exportHTML(const QString &fpath, const QString &dpath,
4781                           bool useDialog)
4782 {
4783     ExportHTML ex(this);
4784     ex.setLastCommand(
4785         settings.localValue(filePath, "/export/last/command", "").toString());
4786
4787     if (!dpath.isEmpty())
4788         ex.setDirPath(dpath);
4789     if (!fpath.isEmpty())
4790         ex.setFilePath(fpath);
4791
4792     ex.doExport(useDialog);
4793 }
4794
4795 void VymModel::exportConfluence(bool createPage, const QString &pageURL,
4796                                 const QString &pageName, bool useDialog)
4797 {
4798     ExportConfluence ex(this);
4799     ex.setCreateNewPage(createPage);
4800     ex.setURL(pageURL);
4801     ex.setPageName(pageName);
4802     ex.setLastCommand(
4803         settings.localValue(filePath, "/export/last/command", "").toString());
4804
4805     ex.doExport(useDialog);
4806 }
4807
4808 void VymModel::exportImpress(const QString &fn, const QString &cf)
4809 {
4810     ExportOO ex;
4811     ex.setFilePath(fn);
4812     ex.setModel(this);
4813     ex.setLastCommand(
4814         settings.localValue(filePath, "/export/last/command", "").toString());
4815
4816     if (ex.setConfigFile(cf)) {
4817         QString lastCommand =
4818             settings.localValue(filePath, "/export/last/command", "")
4819                 .toString();
4820
4821         setExportMode(true);
4822         ex.exportPresentation();
4823         setExportMode(false);
4824
4825         QString command =
4826             settings.localValue(filePath, "/export/last/command", "")
4827                 .toString();
4828         if (lastCommand != command)
4829             setChanged();
4830     }
4831 }
4832
4833 bool VymModel::exportLastAvailable(QString &description, QString &command,
4834                                    QString &dest)
4835 {
4836     command =
4837         settings.localValue(filePath, "/export/last/command", "").toString();
4838
4839     description = settings.localValue(filePath, "/export/last/description", "")
4840                       .toString();
4841     dest = settings.localValue(filePath, "/export/last/displayedDestination", "")
4842                .toString();
4843     if (!command.isEmpty() && command.contains("exportMap"))
4844         return true;
4845     else
4846         return false;
4847 }
4848
4849 void VymModel::setExportLastCommand(const QString &cmd)
4850 {
4851     settings.setLocalValue(filePath, "/export/last/command", cmd);
4852 }
4853
4854 void VymModel::setExportLastDescription(const QString &desc)
4855 {
4856     settings.setLocalValue(filePath, "/export/last/description", desc);
4857 }
4858
4859 void VymModel::setExportLastDestination(const QString &displayedDest)
4860 {
4861     settings.setLocalValue(filePath, "/export/last/displayedDestination", displayedDest);
4862 }
4863
4864 void VymModel::exportLast()
4865 {
4866     QString desc, command,
4867         dest; // FIXME-3 better integrate configFile into command
4868     if (exportLastAvailable(desc, command, dest)) {
4869         //qDebug() << "VM::exportLast: " << command;
4870         execute(command);
4871     }
4872 }
4873
4874 void VymModel::exportLaTeX(const QString &fname, bool askName)
4875 {
4876     ExportLaTeX ex;
4877     ex.setModel(this);
4878     ex.setLastCommand(
4879         settings.localValue(filePath, "/export/last/command", "").toString());
4880
4881     if (fname == "")
4882         ex.setFilePath(mapName + ".tex");
4883     else
4884         ex.setFilePath(fname);
4885
4886     if (askName)
4887         ex.execDialog();
4888     if (!ex.canceled()) {
4889         setExportMode(true);
4890         ex.doExport();
4891         setExportMode(false);
4892     }
4893 }
4894
4895 void VymModel::exportOrgMode(const QString &fname, bool askName)
4896 {
4897     ExportOrgMode ex;
4898     ex.setModel(this);
4899     ex.setLastCommand(
4900         settings.localValue(filePath, "/export/last/command", "").toString());
4901
4902     if (fname == "")
4903         ex.setFilePath(mapName + ".org");
4904     else
4905         ex.setFilePath(fname);
4906
4907     if (askName) {
4908         ex.setDirPath(lastExportDir.absolutePath());
4909         ex.execDialog();
4910     }
4911
4912     if (!ex.canceled()) {
4913         setExportMode(true);
4914         ex.doExport();
4915         setExportMode(false);
4916     }
4917 }
4918
4919 void VymModel::exportMarkdown(const QString &fname, bool askName)
4920 {
4921     ExportMarkdown ex;
4922     ex.setModel(this);
4923     ex.setLastCommand(
4924         settings.localValue(filePath, "/export/last/command", "").toString());
4925
4926     if (fname == "")
4927         ex.setFilePath(mapName + ".md");
4928     else
4929         ex.setFilePath(fname);
4930
4931     if (askName) {
4932         ex.setDirPath(lastExportDir.absolutePath());
4933         ex.execDialog();
4934     }
4935
4936     if (!ex.canceled()) {
4937         setExportMode(true);
4938         ex.doExport();
4939         setExportMode(false);
4940     }
4941 }
4942 //////////////////////////////////////////////
4943 // View related
4944 //////////////////////////////////////////////
4945
4946 void VymModel::registerMapEditor(QWidget *e) { mapEditor = (MapEditor *)e; }
4947
4948 void VymModel::setMapZoomFactor(const double &d)
4949 {
4950     zoomFactor = d;
4951     mapEditor->setZoomFactorTarget(d);
4952 }
4953
4954 void VymModel::setMapRotationAngle(const double &d)
4955 {
4956     rotationAngle = d;
4957     mapEditor->setAngleTarget(d);
4958 }
4959
4960 void VymModel::setMapAnimDuration(const int &d) { animDuration = d; }
4961
4962 void VymModel::setMapAnimCurve(const QEasingCurve &c) { animCurve = c; }
4963
4964 bool VymModel::centerOnID(const QString &id)
4965 {
4966     TreeItem *ti = findUuid(QUuid(id));
4967     if (ti) {
4968         LinkableMapObj *lmo = ((MapItem *)ti)->getLMO();
4969         if (zoomFactor > 0 && lmo) {
4970             mapEditor->setViewCenterTarget(lmo->getBBox().center(), zoomFactor,
4971                                            rotationAngle, animDuration,
4972                                            animCurve);
4973             return true;
4974         }
4975     }
4976     return false;
4977 }
4978
4979 void VymModel::setContextPos(QPointF p)
4980 {
4981     contextPos = p;
4982     hasContextPos = true;
4983 }
4984
4985 void VymModel::unsetContextPos()
4986 {
4987     contextPos = QPointF();
4988     hasContextPos = false;
4989 }
4990
4991 void VymModel::reposition()
4992 {
4993     if (repositionBlocked)
4994         return;
4995
4996     BranchObj *bo;
4997     for (int i = 0; i < rootItem->branchCount(); i++) {
4998         bo = rootItem->getBranchObjNum(i);
4999         if (bo)
5000             bo->reposition(); //  for positioning heading
5001         else
5002             qDebug() << "VM::reposition bo=0";
5003     }
5004     mapEditor->getTotalBBox();
5005
5006     // required to *reposition* the selection box. size is already correct:
5007     emitSelectionChanged(); //FIXME-2 better only update selection geometry
5008 }
5009
5010 bool VymModel::setMapLinkStyle(const QString &s)
5011 {
5012     QString snow;
5013     switch (linkstyle) {
5014     case LinkableMapObj::Line:
5015         snow = "StyleLine";
5016         break;
5017     case LinkableMapObj::Parabel:
5018         snow = "StyleParabel";
5019         break;
5020     case LinkableMapObj::PolyLine:
5021         snow = "StylePolyLine";
5022         break;
5023     case LinkableMapObj::PolyParabel:
5024         snow = "StylePolyParabel";
5025         break;
5026     default:
5027         return false;
5028         break;
5029     }
5030
5031     saveState(QString("setMapLinkStyle (\"%1\")").arg(s),
5032               QString("setMapLinkStyle (\"%1\")").arg(snow),
5033               QString("Set map link style (\"%1\")").arg(s));
5034
5035     if (s == "StyleLine")
5036         linkstyle = LinkableMapObj::Line;
5037     else if (s == "StyleParabel")
5038         linkstyle = LinkableMapObj::Parabel;
5039     else if (s == "StylePolyLine")
5040         linkstyle = LinkableMapObj::PolyLine;
5041     else if (s == "StylePolyParabel")
5042         linkstyle = LinkableMapObj::PolyParabel;
5043     else
5044         linkstyle = LinkableMapObj::UndefinedStyle;
5045
5046     BranchItem *cur = NULL;
5047     BranchItem *prev = NULL;
5048     BranchObj *bo;
5049     nextBranch(cur, prev);
5050     while (cur) {
5051         bo = (BranchObj *)(cur->getLMO());
5052         bo->setLinkStyle(bo->getDefLinkStyle(
5053             cur->parent())); // FIXME-4 better emit dataCHanged and leave the
5054                              // changes to View
5055         nextBranch(cur, prev);
5056     }
5057     reposition();
5058     return true;
5059 }
5060
5061 LinkableMapObj::Style VymModel::getMapLinkStyle() { return linkstyle; }
5062
5063 uint VymModel::getModelID() { return modelID; }
5064
5065 void VymModel::setView(VymView *vv) { vymView = vv; }
5066
5067 void VymModel::setMapDefLinkColor(QColor col)
5068 {
5069     if (!col.isValid())
5070         return;
5071     saveState(
5072         QString("setMapDefLinkColor (\"%1\")").arg(getMapDefLinkColor().name()),
5073         QString("setMapDefLinkColor (\"%1\")").arg(col.name()),
5074         QString("Set map link color to %1").arg(col.name()));
5075
5076     defLinkColor = col;
5077
5078     // Set color for "link arrows" in TreeEditor
5079     vymView->setLinkColor(col);
5080
5081     BranchItem *cur = NULL;
5082     BranchItem *prev = NULL;
5083     BranchObj *bo;
5084     nextBranch(cur, prev);
5085     while (cur) {
5086         bo = (BranchObj *)(cur->getLMO());
5087         bo->setLinkColor();
5088
5089         for (int i = 0; i < cur->imageCount(); ++i)
5090             cur->getImageNum(i)->getLMO()->setLinkColor();
5091
5092         nextBranch(cur, prev);
5093     }
5094     updateActions();
5095 }
5096
5097 void VymModel::setMapLinkColorHintInt()
5098 {
5099     // called from setMapLinkColorHint(lch) or at end of parse
5100     BranchItem *cur = NULL;
5101     BranchItem *prev = NULL;
5102     BranchObj *bo;
5103     nextBranch(cur, prev);
5104     while (cur) {
5105         bo = (BranchObj *)(cur->getLMO());
5106         bo->setLinkColor();
5107
5108         for (int i = 0; i < cur->imageCount(); ++i)
5109             cur->getImageNum(i)->getLMO()->setLinkColor();
5110
5111         nextBranch(cur, prev);
5112     }
5113 }
5114
5115 void VymModel::setMapLinkColorHint(LinkableMapObj::ColorHint lch)
5116 {
5117     linkcolorhint = lch;
5118     setMapLinkColorHintInt();
5119 }
5120
5121 void VymModel::toggleMapLinkColorHint()
5122 {
5123     if (linkcolorhint == LinkableMapObj::HeadingColor)
5124         linkcolorhint = LinkableMapObj::DefaultColor;
5125     else
5126         linkcolorhint = LinkableMapObj::HeadingColor;
5127     BranchItem *cur = NULL;
5128     BranchItem *prev = NULL;
5129     BranchObj *bo;
5130     nextBranch(cur, prev);
5131     while (cur) {
5132         bo = (BranchObj *)(cur->getLMO());
5133         bo->setLinkColor();
5134
5135         for (int i = 0; i < cur->imageCount(); ++i)
5136             cur->getImageNum(i)->getLMO()->setLinkColor();
5137
5138         nextBranch(cur, prev);
5139     }
5140 }
5141
5142 void VymModel::
5143     selectMapBackgroundImage() // FIXME-3 for using background image:
5144                                // view.setCacheMode(QGraphicsView::CacheBackground);
5145                                // Also this belongs into ME
5146 {
5147     QStringList filters;
5148     filters << tr("Images") +
5149                    " (*.png *.bmp *.xbm *.jpg *.png *.xpm *.gif *.pnm)";
5150     QFileDialog fd;
5151     fd.setFileMode(QFileDialog::ExistingFile);
5152     fd.setWindowTitle(vymName + " - " + tr("Load background image"));
5153     fd.setDirectory(lastImageDir);
5154     fd.setAcceptMode(QFileDialog::AcceptOpen);
5155
5156     if (fd.exec() == QDialog::Accepted && !fd.selectedFiles().isEmpty()) {
5157         // TODO selectMapBackgroundImg in QT4 use:  lastImageDir=fd.directory();
5158         lastImageDir = QDir(fd.directory().path());
5159         setMapBackgroundImage(fd.selectedFiles().first());
5160     }
5161 }
5162
5163 void VymModel::setMapBackgroundImage(
5164     const QString &fn) // FIXME-3 missing savestate, move to ME
5165 {
5166     /*
5167     QColor oldcol=mapEditor->getScene()->backgroundBrush().color();
5168     saveState(
5169     selection,
5170     QString ("setMapBackgroundImage (%1)").arg(oldcol.name()),
5171     selection,
5172     QString ("setMapBackgroundImage (%1)").arg(col.name()),
5173     QString("Set background color of map to %1").arg(col.name()));
5174     */
5175     QBrush brush;
5176     brush.setTextureImage(QImage(fn));
5177     mapEditor->getScene()->setBackgroundBrush(brush);
5178 }
5179
5180 void VymModel::selectMapBackgroundColor()
5181 {
5182     QColor col = QColorDialog::getColor(
5183         mapEditor->getScene()->backgroundBrush().color(), NULL);
5184     if (!col.isValid())
5185         return;
5186     setMapBackgroundColor(col);
5187 }
5188
5189 void VymModel::setMapBackgroundColor(QColor col)
5190 {
5191     QColor oldcol = mapEditor->getScene()->backgroundBrush().color();
5192     saveState(QString("setMapBackgroundColor (\"%1\")").arg(oldcol.name()),
5193               QString("setMapBackgroundColor (\"%1\")").arg(col.name()),
5194               QString("Set background color of map to %1").arg(col.name()));
5195     backgroundColor = col;  // Used for backroundRole in TreeModel::data()
5196     vymView->setBackgroundColor(backgroundColor);
5197 }
5198
5199 QColor VymModel::getMapBackgroundColor() // FIXME-4 move to ME
5200 {
5201     return mapEditor->getScene()->backgroundBrush().color();
5202 }
5203
5204 QFont VymModel::getMapDefaultFont() { return defaultFont; }
5205
5206 void VymModel::setMapDefaultFont(const QFont &f) { defaultFont = f; }
5207
5208 LinkableMapObj::ColorHint VymModel::getMapLinkColorHint() // FIXME-4 move to ME
5209 {
5210     return linkcolorhint;
5211 }
5212
5213 QColor VymModel::getMapDefLinkColor() // FIXME-4 move to ME
5214 {
5215     return defLinkColor;
5216 }
5217
5218 void VymModel::setMapDefXLinkPen(const QPen &p) // FIXME-4 move to ME
5219 {
5220     defXLinkPen = p;
5221 }
5222
5223 QPen VymModel::getMapDefXLinkPen() // FIXME-4 move to ME
5224 {
5225     return defXLinkPen;
5226 }
5227
5228 void VymModel::setMapDefXLinkStyleBegin(const QString &s)
5229 {
5230     defXLinkStyleBegin = s;
5231 }
5232
5233 QString VymModel::getMapDefXLinkStyleBegin() { return defXLinkStyleBegin; }
5234
5235 void VymModel::setMapDefXLinkStyleEnd(const QString &s)
5236 {
5237     defXLinkStyleEnd = s;
5238 }
5239
5240 QString VymModel::getMapDefXLinkStyleEnd() { return defXLinkStyleEnd; }
5241
5242 void VymModel::move(const double &x, const double &y)
5243 {
5244     MapItem *seli = (MapItem *)getSelectedItem();
5245     if (seli &&
5246         (seli->isBranchLikeType() || seli->getType() == TreeItem::Image)) {
5247         LinkableMapObj *lmo = seli->getLMO();
5248         if (lmo) {
5249             QPointF ap(lmo->getAbsPos());
5250             QPointF to(x, y);
5251             if (ap != to) {
5252                 QString ps = qpointFToString(ap);
5253                 QString s = getSelectString(seli);
5254                 saveState(
5255                     s, "move " + ps, s, "move " + qpointFToString(to),
5256                     QString("Move %1 to %2").arg(getObjectName(seli)).arg(ps));
5257                 lmo->move(x, y);
5258                 reposition();
5259                 emitSelectionChanged();
5260             }
5261         }
5262     }
5263 }
5264
5265 void VymModel::moveRel(const double &x, const double &y)
5266 {
5267     MapItem *seli = (MapItem *)getSelectedItem();
5268     if (seli &&
5269         (seli->isBranchLikeType() || seli->getType() == TreeItem::Image)) {
5270         LinkableMapObj *lmo = seli->getLMO();
5271         if (lmo) {
5272             QPointF rp(lmo->getRelPos());
5273             QPointF to(x, y);
5274             if (rp != to) {
5275                 QString ps = qpointFToString(lmo->getRelPos());
5276                 QString s = getSelectString(seli);
5277                 saveState(s, "moveRel " + ps, s,
5278                           "moveRel " + qpointFToString(to),
5279                           QString("Move %1 to relative position %2")
5280                               .arg(getObjectName(seli))
5281                               .arg(ps));
5282                 ((OrnamentedObj *)lmo)->move2RelPos(x, y);
5283                 reposition();
5284                 lmo->updateLinkGeometry();
5285                 emitSelectionChanged();
5286             }
5287         }
5288     }
5289 }
5290
5291 void VymModel::animate()
5292 {
5293     animationTimer->stop();
5294     BranchObj *bo;
5295     int i = 0;
5296     while (i < animObjList.size()) {
5297         bo = (BranchObj *)animObjList.at(i);
5298         if (!bo->animate()) {
5299             if (i >= 0) {
5300                 animObjList.removeAt(i);
5301                 i--;
5302             }
5303         }
5304         bo->reposition();
5305         i++;
5306     }
5307     emitSelectionChanged();
5308
5309     if (!animObjList.isEmpty())
5310         animationTimer->start(animationInterval);
5311 }
5312
5313 void VymModel::startAnimation(BranchObj *bo, const QPointF &v)
5314 {
5315     if (!bo)
5316         return;
5317
5318     if (bo->getUseRelPos())
5319         startAnimation(bo, bo->getRelPos(), bo->getRelPos() + v);
5320     else
5321         startAnimation(bo, bo->getAbsPos(), bo->getAbsPos() + v);
5322 }
5323
5324 void VymModel::startAnimation(BranchObj *bo, const QPointF &start,
5325                               const QPointF &dest)
5326 {
5327     if (start == dest)
5328         return;
5329     if (bo && bo->getTreeItem()->depth() >= 0) {
5330         AnimPoint ap;
5331         ap.setStart(start);
5332         ap.setDest(dest);
5333         ap.setTicks(animationTicks);
5334         ap.setAnimated(true);
5335         bo->setAnimation(ap);
5336         if (!animObjList.contains(bo))
5337             animObjList.append(bo);
5338         animationTimer->setSingleShot(true);
5339         animationTimer->start(animationInterval);
5340     }
5341 }
5342
5343 void VymModel::stopAnimation(MapObj *mo)
5344 {
5345     int i = animObjList.indexOf(mo);
5346     if (i >= 0)
5347         animObjList.removeAt(i);
5348 }
5349
5350 void VymModel::stopAllAnimation()
5351 {
5352     BranchObj *bo;
5353     int i = 0;
5354     while (i < animObjList.size()) {
5355         bo = (BranchObj *)animObjList.at(i);
5356         bo->stopAnimation();
5357         bo->requestReposition();
5358         i++;
5359     }
5360     reposition();
5361 }
5362
5363 void VymModel::sendSelection()
5364 {
5365     if (netstate != Server)
5366         return;
5367     sendData(QString("select (\"%1\")").arg(getSelectString()));
5368 }
5369
5370 void VymModel::newServer()
5371 {
5372     port = 54321;
5373     sendCounter = 0;
5374     tcpServer = new QTcpServer(this);
5375     if (!tcpServer->listen(QHostAddress::Any, port)) {
5376         QMessageBox::critical(NULL, "vym server",
5377                               QString("Unable to start the server: %1.")
5378                                   .arg(tcpServer->errorString()));
5379         // FIXME-3 needed? we are no widget any longer... close();
5380         return;
5381     }
5382     connect(tcpServer, SIGNAL(newConnection()), this, SLOT(newClient()));
5383     netstate = Server;
5384     qDebug() << "Server is running on port " << tcpServer->serverPort();
5385 }
5386
5387 void VymModel::connectToServer()
5388 {
5389     port = 54321;
5390     server = "salam.suse.de";
5391     server = "localhost";
5392     clientSocket = new QTcpSocket(this);
5393     clientSocket->abort();
5394     clientSocket->connectToHost(server, port);
5395     connect(clientSocket, SIGNAL(readyRead()), this, SLOT(readData()));
5396     connect(clientSocket, SIGNAL(error(QAbstractSocket::SocketError)), this,
5397             SLOT(displayNetworkError(QAbstractSocket::SocketError)));
5398     netstate = Client;
5399     qDebug() << "connected to " << qPrintable(server) << " port " << port;
5400 }
5401
5402 void VymModel::newClient()
5403 {
5404     QTcpSocket *newClient = tcpServer->nextPendingConnection();
5405     connect(newClient, SIGNAL(disconnected()), newClient, SLOT(deleteLater()));
5406
5407     qDebug() << "ME::newClient  at "
5408              << qPrintable(newClient->peerAddress().toString());
5409
5410     clientList.append(newClient);
5411 }
5412
5413 void VymModel::sendData(const QString &s)
5414 {
5415     if (clientList.size() == 0)
5416         return;
5417
5418     // Create bytearray to send
5419     QByteArray block;
5420     QDataStream out(&block, QIODevice::WriteOnly);
5421     out.setVersion(QDataStream::Qt_4_0);
5422
5423     // Reserve some space for blocksize
5424     out << (quint16)0;
5425
5426     // Write sendCounter
5427     out << sendCounter++;
5428
5429     // Write data
5430     out << s;
5431
5432     // Go back and write blocksize so far
5433     out.device()->seek(0);
5434     quint16 bs = (quint16)(block.size() - 2 * sizeof(quint16));
5435     out << bs;
5436
5437     if (debug)
5438         qDebug() << "ME::sendData  bs=" << bs << "  counter=" << sendCounter
5439                  << "  s=" << qPrintable(s);
5440
5441     for (int i = 0; i < clientList.size(); ++i) {
5442         // qDebug() << "Sending \""<<qPrintable (s)<<"\" to "<<qPrintable
5443         // (clientList.at(i)->peerAddress().toString());
5444         clientList.at(i)->write(block);
5445     }
5446 }
5447
5448 void VymModel::readData()
5449 {
5450     while (clientSocket->bytesAvailable() >= (int)sizeof(quint16)) {
5451         if (debug)
5452             qDebug() << "readData  bytesAvail="
5453                      << clientSocket->bytesAvailable();
5454         quint16 recCounter;
5455         quint16 blockSize;
5456
5457         QDataStream in(clientSocket);
5458         in.setVersion(QDataStream::Qt_4_0);
5459
5460         in >> blockSize;
5461         in >> recCounter;
5462
5463         QString t;
5464         in >> t;
5465         if (debug)
5466             qDebug() << "VymModel::readData  command=" << qPrintable(t);
5467         // bool noErr;
5468         // QString errMsg;
5469         // parseAtom (t,noErr,errMsg);    //FIXME-4 needs rework using scripts
5470     }
5471     return;
5472 }
5473
5474 void VymModel::displayNetworkError(QAbstractSocket::SocketError socketError)
5475 {
5476     switch (socketError) {
5477     case QAbstractSocket::RemoteHostClosedError:
5478         break;
5479     case QAbstractSocket::HostNotFoundError:
5480         QMessageBox::information(NULL, vymName + " Network client",
5481                                  "The host was not found. Please check the "
5482                                  "host name and port settings.");
5483         break;
5484     case QAbstractSocket::ConnectionRefusedError:
5485         QMessageBox::information(NULL, vymName + " Network client",
5486                                  "The connection was refused by the peer. "
5487                                  "Make sure the fortune server is running, "
5488                                  "and check that the host name and port "
5489                                  "settings are correct.");
5490         break;
5491     default:
5492         QMessageBox::information(NULL, vymName + " Network client",
5493                                  QString("The following error occurred: %1.")
5494                                      .arg(clientSocket->errorString()));
5495     }
5496 }
5497
5498 void VymModel::downloadImage(const QUrl &url, BranchItem *bi)
5499 {
5500     if (!bi)
5501         bi = getSelectedBranch();
5502     if (!bi) {
5503         qWarning("VM::download bi==NULL");
5504         return;
5505     }
5506
5507     // FIXME-3 download img to tmpfile and delete after running script in
5508     // mainWindow
5509     QString script;
5510     script += QString("m = vym.currentMap();m.selectID(\"%1\");")
5511                   .arg(bi->getUuid().toString());
5512     script += QString("m.loadImage(\"$TMPFILE\");");
5513
5514     DownloadAgent *agent = new DownloadAgent(url);
5515     agent->setFinishedAction(this, script);
5516     connect(agent, SIGNAL(downloadFinished()), mainWindow,
5517             SLOT(downloadFinished()));
5518     QTimer::singleShot(0, agent, SLOT(execute()));
5519 }
5520
5521 void VymModel::selectMapSelectionColor()    // FIXME-2 move out of VymModel, consider Pen/Brush
5522 {
5523     QColor col = QColorDialog::getColor(defLinkColor, NULL);
5524     setSelectionPenColor(col);
5525     setSelectionBrushColor(col);
5526 }
5527
5528 void VymModel::emitSelectionChanged(const QItemSelection &newsel)
5529 {
5530     emit(selectionChanged(newsel,
5531                           newsel)); // needed e.g. to update geometry in editor
5532     sendSelection();
5533 }
5534
5535 void VymModel::emitSelectionChanged()
5536 {
5537     QItemSelection newsel = selModel->selection();
5538     emitSelectionChanged(newsel);
5539 }
5540
5541 void VymModel::setSelectionPenColor(QColor col)
5542 {
5543     if (!col.isValid())
5544         return;
5545
5546     QPen selPen = mapEditor->getSelectionPen();
5547     saveState(QString("setSelectionPenColor (\"%1\")")
5548                   .arg(selPen.color().name()),
5549               QString("setSelectionPenColor (\"%1\")").arg(col.name()),
5550               QString("Set pen color of selection box to %1").arg(col.name()));
5551
5552     selPen.setColor(col);
5553     mapEditor->setSelectionPen(selPen);
5554 }
5555
5556 QColor VymModel::getSelectionPenColor() {
5557     return mapEditor->getSelectionPen().color();
5558 }
5559
5560 void VymModel::setSelectionPenWidth(qreal w)
5561 {
5562     QPen selPen = mapEditor->getSelectionPen();
5563     
5564     saveState(QString("setSelectionPenWidth (\"%1\")")
5565                   .arg(mapEditor->getSelectionPen().width()),
5566               QString("setSelectionPenWidth (\"%1\")").arg(w),
5567               QString("Set pen width of selection box to %1").arg(w));
5568
5569     selPen.setWidth(w);
5570     mapEditor->setSelectionPen(selPen);
5571     //vymView->setSelectionColor(col);
5572 }
5573
5574 qreal VymModel::getSelectionPenWidth() {
5575     return mapEditor->getSelectionPen().width();
5576 }
5577
5578 void VymModel::setSelectionBrushColor(QColor col)
5579 {
5580     if (!col.isValid())
5581         return;
5582
5583     QBrush selBrush = mapEditor->getSelectionBrush();
5584     saveState(QString("setSelectionBrushColor (\"%1\")")
5585                   .arg(selBrush.color().name()),
5586               QString("setSelectionBrushColor (\"%1\")").arg(col.name()),
5587               QString("Set Brush color of selection box to %1").arg(col.name()));
5588
5589     selBrush.setColor(col);
5590     vymView->setSelectionBrush(selBrush);
5591 }
5592
5593 QColor VymModel::getSelectionBrushColor() {
5594     return mapEditor->getSelectionBrush().color();
5595 }
5596
5597 bool VymModel::initIterator(const QString &iname, bool deepLevelsFirst)
5598 {
5599     Q_UNUSED(deepLevelsFirst);
5600
5601     // Remove existing iterators first
5602     selIterCur.remove(iname);
5603     selIterPrev.remove(iname);
5604     selIterStart.remove(iname);
5605     selIterActive.remove(iname);
5606
5607     QList<BranchItem *> selbis;
5608     selbis = getSelectedBranches();
5609     if (selbis.count() == 1) {
5610         BranchItem *prev = NULL;
5611         BranchItem *cur = NULL;
5612         nextBranch(cur, prev, false, selbis.first());
5613         if (cur) {
5614             selIterCur.insert(iname, cur->getUuid());
5615             selIterPrev.insert(iname, prev->getUuid());
5616             selIterStart.insert(iname, selbis.first()->getUuid());
5617             selIterActive.insert(iname, false);
5618             // qDebug() << "Created iterator " << iname;
5619             return true;
5620         }
5621     }
5622     return false;
5623 }
5624
5625 bool VymModel::nextIterator(const QString &iname)
5626 {
5627     if (selIterCur.keys().indexOf(iname) < 0) {
5628         qWarning()
5629             << QString("VM::nextIterator couldn't find %1 in hash of iterators")
5630                    .arg(iname);
5631         return false;
5632     }
5633
5634     BranchItem *cur = (BranchItem *)(findUuid(selIterCur.value(iname)));
5635     if (!cur) {
5636         qWarning() << "VM::nextIterator couldn't find cur" << selIterCur;
5637         return false;
5638     }
5639
5640     qDebug() << "  " << iname << "selecting " << cur->getHeadingPlain();
5641     select(cur);
5642
5643     if (!selIterActive.value(iname)) {
5644         // Select for the first time
5645         select(cur);
5646         selIterActive[iname] = true;
5647         return true;
5648     }
5649
5650     BranchItem *prev = (BranchItem *)(findUuid(selIterPrev.value(iname)));
5651     BranchItem *start = (BranchItem *)(findUuid(selIterStart.value(iname)));
5652     if (!prev)
5653         qWarning() << "VM::nextIterator couldn't find prev"
5654                    << selIterPrev.value(iname);
5655     if (!start)
5656         qWarning() << "VM::nextIterator couldn't find start "
5657                    << selIterStart.value(iname);
5658
5659     if (cur && prev && start) {
5660         nextBranch(cur, prev, false, start);
5661         if (cur) {
5662             selIterCur[iname] = cur->getUuid();
5663             selIterPrev[iname] = prev->getUuid();
5664             select(cur);
5665             return true;
5666         }
5667         else
5668             return false;
5669     }
5670     return false;
5671 }
5672
5673 void VymModel::setHideTmpMode(TreeItem::HideTmpMode mode)
5674 {
5675     hidemode = mode;
5676     for (int i = 0; i < rootItem->branchCount(); i++)
5677         rootItem->getBranchNum(i)->setHideTmp(mode);
5678     reposition();
5679     if (mode == TreeItem::HideExport)
5680         unselectAll();
5681     else
5682         reselect();
5683
5684     qApp->processEvents();
5685 }
5686
5687 //////////////////////////////////////////////
5688 // Selection related
5689 //////////////////////////////////////////////
5690
5691 void VymModel::updateSelection(QItemSelection newsel, QItemSelection dsel)
5692 {
5693     QModelIndex ix;
5694     MapItem *mi;
5695     BranchItem *bi;
5696     bool do_reposition = false;
5697     foreach (ix, dsel.indexes()) {
5698         mi = static_cast<MapItem *>(ix.internalPointer());
5699         if (mi->isBranchLikeType())
5700             do_reposition =
5701                 do_reposition || ((BranchItem *)mi)->resetTmpUnscroll();
5702         if (mi->getType() == TreeItem::XLink) {
5703             Link *li = ((XLinkItem *)mi)->getLink();
5704             XLinkObj *xlo = li->getXLinkObj();
5705             if (xlo)
5706                 xlo->setSelection(XLinkObj::Unselected);
5707
5708             do_reposition =
5709                 do_reposition || li->getBeginBranch()->resetTmpUnscroll();
5710             do_reposition =
5711                 do_reposition || li->getEndBranch()->resetTmpUnscroll();
5712         }
5713     }
5714
5715     foreach (ix, newsel.indexes()) {
5716         mi = static_cast<MapItem *>(ix.internalPointer());
5717         if (mi->isBranchLikeType()) {
5718             bi = (BranchItem *)mi;
5719             if (bi->hasScrolledParent()) {
5720                 bi->tmpUnscroll();
5721                 do_reposition = true;
5722             }
5723         }
5724         if (mi->getType() == TreeItem::XLink) {
5725             ((XLinkItem *)mi)->setSelection();
5726
5727             // begin/end branches need to be tmp unscrolled
5728             Link *li = ((XLinkItem *)mi)->getLink();
5729             bi = li->getBeginBranch();
5730             if (bi->hasScrolledParent()) {
5731                 bi->tmpUnscroll();
5732                 do_reposition = true;
5733             }
5734             bi = li->getEndBranch();
5735             if (bi->hasScrolledParent()) {
5736                 bi->tmpUnscroll();
5737                 do_reposition = true;
5738             }
5739         }
5740     }
5741     if (do_reposition)
5742         reposition();
5743 }
5744
5745 void VymModel::setSelectionModel(QItemSelectionModel *sm) { selModel = sm; }
5746
5747 QItemSelectionModel *VymModel::getSelectionModel() { return selModel; }
5748
5749 void VymModel::setSelectionBlocked(bool b) { selectionBlocked = b; }
5750
5751 bool VymModel::isSelectionBlocked() { return selectionBlocked; }
5752
5753 bool VymModel::select(const QString &s) // FIXME-2 Does not support multiple selections yet
5754 {
5755     if (s.isEmpty())
5756         return false;
5757     TreeItem *ti = findBySelectString(s);
5758     if (ti)
5759         return select(index(ti));
5760     return false;
5761 }
5762
5763 bool VymModel::selectID(const QString &s)
5764 {
5765     if (s.isEmpty())
5766         return false;
5767     TreeItem *ti = findUuid(QUuid(s));
5768     if (ti)
5769         return select(index(ti));
5770     return false;
5771 }
5772
5773 bool VymModel::select(LinkableMapObj *lmo)
5774 {
5775     QItemSelection oldsel = selModel->selection();
5776
5777     if (lmo)
5778         return select(lmo->getTreeItem());
5779     else
5780         return false;
5781 }
5782
5783 bool VymModel::selectToggle(TreeItem *ti)
5784 {
5785     if (ti) {
5786         selModel->select(index(ti), QItemSelectionModel::Toggle);
5787         // appendSelectionToHistory();  // FIXME-4 selection history not implemented yet
5788         // for multiselections
5789         lastToggledUuid = ti->getUuid();
5790         return true;
5791     }
5792     return false;
5793 }
5794
5795 bool VymModel::selectToggle(const QString &selectString)
5796 {
5797     TreeItem *ti = findBySelectString(selectString);
5798     return selectToggle(ti);
5799 }
5800
5801 bool VymModel::select(TreeItem *ti)
5802 {
5803     if (ti)
5804         return select(index(ti));
5805     else
5806         return false;
5807 }
5808
5809 bool VymModel::select(const QModelIndex &index)
5810 {
5811     if (index.isValid()) {
5812         TreeItem *ti = getItem(index);
5813         if (ti->isBranchLikeType()) {
5814             if (((BranchItem *)ti)->tmpUnscroll())
5815                 reposition();
5816         }
5817         selModel->select(index, QItemSelectionModel::ClearAndSelect);
5818         appendSelectionToHistory();
5819         return true;
5820     }
5821     return false;
5822 }
5823
5824 void VymModel::unselectAll() { unselect(selModel->selection()); }
5825
5826 void VymModel::unselect(QItemSelection desel)
5827 {
5828     if (!desel.isEmpty()) {
5829         lastSelectString = getSelectString();
5830         selModel->clearSelection();
5831     }
5832 }
5833
5834 bool VymModel::reselect()
5835 {
5836     bool b = select(lastSelectString);
5837     return b;
5838 }
5839
5840 bool VymModel::canSelectPrevious()
5841 {
5842     if (currentSelection > 0)
5843         return true;
5844     else
5845         return false;
5846 }
5847
5848 bool VymModel::selectPrevious()
5849 {
5850     keepSelectionHistory = true;
5851     bool result = false;
5852     while (currentSelection > 0) {
5853         currentSelection--;
5854         TreeItem *ti = findID(selectionHistory.at(currentSelection));
5855         if (ti) {
5856             result = select(ti);
5857             break;
5858         }
5859         else
5860             selectionHistory.removeAt(currentSelection);
5861     }
5862     keepSelectionHistory = false;
5863     return result;
5864 }
5865
5866 bool VymModel::canSelectNext()
5867 {
5868     if (currentSelection < selectionHistory.count() - 1)
5869         return true;
5870     else
5871         return false;
5872 }
5873
5874 bool VymModel::selectNext()
5875 {
5876     keepSelectionHistory = true;
5877     bool result = false;
5878     while (currentSelection < selectionHistory.count() - 1) {
5879         currentSelection++;
5880         TreeItem *ti = findID(selectionHistory.at(currentSelection));
5881         if (ti) {
5882             result = select(ti);
5883             break;
5884         }
5885         else
5886             selectionHistory.removeAt(currentSelection);
5887     }
5888     keepSelectionHistory = false;
5889     return result;
5890 }
5891
5892 void VymModel::resetSelectionHistory()
5893 {
5894     selectionHistory.clear();
5895     currentSelection = -1;
5896     keepSelectionHistory = false;
5897     appendSelectionToHistory();
5898 }
5899
5900 void VymModel::appendSelectionToHistory() // FIXME-4 history unable to cope with multiple
5901                                           // selections
5902 {
5903     uint id = 0;
5904     TreeItem *ti = getSelectedItem();
5905     if (ti && !keepSelectionHistory) {
5906         if (ti->isBranchLikeType())
5907             ((BranchItem *)ti)->setLastSelectedBranch();
5908         id = ti->getID();
5909         selectionHistory.append(id);
5910         currentSelection = selectionHistory.count() - 1;
5911         updateActions();
5912     }
5913 }
5914
5915 void VymModel::emitShowSelection(bool scaled)
5916 {
5917     if (!repositionBlocked)
5918         emit(showSelection(scaled));
5919 }
5920
5921 TreeItem* VymModel::lastToggledItem()
5922 {
5923     return findUuid(lastToggledUuid);
5924 }
5925
5926 void VymModel::emitNoteChanged(TreeItem *ti)
5927 {
5928     QModelIndex ix = index(ti);
5929     emit(noteChanged(ix));
5930     mainWindow->updateNoteEditor(ti);
5931 }
5932
5933 void VymModel::emitDataChanged(TreeItem *ti)
5934 {
5935     QModelIndex ix = index(ti);
5936     emit(dataChanged(ix, ix));
5937     emitSelectionChanged();
5938     if (!repositionBlocked) {
5939         // Update taskmodel and recalc priorities there
5940         if (ti->isBranchLikeType() && ((BranchItem *)ti)->getTask()) {
5941             taskModel->emitDataChanged(((BranchItem *)ti)->getTask());
5942             taskModel->recalcPriorities();
5943         }
5944     }
5945 }
5946
5947 void VymModel::emitUpdateQueries()
5948 {
5949     // Used to tell MainWindow to update query results
5950     if (repositionBlocked)
5951         return;
5952     emit(updateQueries(this));
5953 }
5954 void VymModel::emitUpdateLayout()
5955 {
5956     if (settings.value("/mainwindow/autoLayout/use", "true") == "true")
5957         emit(updateLayout());
5958 }
5959
5960 bool VymModel::selectFirstBranch()
5961 {
5962     TreeItem *ti = getSelectedBranch();
5963     if (ti) {
5964         TreeItem *par = ti->parent();
5965         if (par) {
5966             TreeItem *ti2 = par->getFirstBranch();
5967             if (ti2)
5968                 return select(ti2);
5969         }
5970     }
5971     return false;
5972 }
5973
5974 bool VymModel::selectFirstChildBranch()
5975 {
5976     TreeItem *ti = getSelectedBranch();
5977     if (ti) {
5978         BranchItem *bi = ti->getFirstBranch();
5979         if (bi)
5980             return select(bi);
5981     }
5982     return false;
5983 }
5984
5985 bool VymModel::selectLastBranch()
5986 {
5987     TreeItem *ti = getSelectedBranch();
5988     if (ti) {
5989         TreeItem *par = ti->parent();
5990         if (par) {
5991             TreeItem *ti2 = par->getLastBranch();
5992             if (ti2)
5993                 return select(ti2);
5994         }
5995     }
5996     return false;
5997 }
5998
5999 bool VymModel::selectLastChildBranch()
6000 {
6001     TreeItem *ti = getSelectedBranch();
6002     if (ti) {
6003         BranchItem *bi = ti->getLastBranch();
6004         if (bi)
6005             return select(bi);
6006     }
6007     return false;
6008 }
6009
6010 bool VymModel::selectLastSelectedBranch()
6011 {
6012     BranchItem *bi = getSelectedBranch();
6013     if (bi) {
6014         bi = bi->getLastSelectedBranch();
6015         if (bi)
6016             return select(bi);
6017     }
6018     return false;
6019 }
6020
6021 bool VymModel::selectLastImage()
6022 {
6023     TreeItem *ti = getSelectedBranch();
6024     if (ti) {
6025         TreeItem *par = ti->parent();
6026         if (par) {
6027             TreeItem *ti2 = par->getLastImage();
6028             if (ti2)
6029                 return select(ti2);
6030         }
6031     }
6032     return false;
6033 }
6034
6035 bool VymModel::selectLatestAdded() { return select(latestAddedItem); }
6036
6037 bool VymModel::selectParent()
6038 {
6039     TreeItem *ti = getSelectedItem();
6040     TreeItem *par;
6041     if (ti) {
6042         par = ti->parent();
6043         if (par)
6044             return select(par);
6045     }
6046     return false;
6047 }
6048
6049 TreeItem::Type VymModel::selectionType()
6050 {
6051     TreeItem *ti = getSelectedItem();
6052     if (ti)
6053         return ti->getType();
6054     else
6055         return TreeItem::Undefined;
6056 }
6057
6058 LinkableMapObj *VymModel::getSelectedLMO()
6059 {
6060     QModelIndexList list = selModel->selectedIndexes();
6061     if (list.count() == 1) {
6062         TreeItem *ti = getItem(list.first());
6063         TreeItem::Type type = ti->getType();
6064         if (type == TreeItem::Branch || type == TreeItem::MapCenter ||
6065             type == TreeItem::Image)
6066             return ((MapItem *)ti)->getLMO();
6067     }
6068     return NULL;
6069 }
6070
6071 BranchObj *VymModel::getSelectedBranchObj() // convenience function
6072 {
6073     TreeItem *ti = getSelectedBranch();
6074     if (ti)
6075         return (BranchObj *)(((MapItem *)ti)->getLMO());
6076     else
6077         return NULL;
6078 }
6079
6080 BranchItem *VymModel::getSelectedBranch()
6081 {
6082     TreeItem *ti = getSelectedItem();
6083     if (ti) {
6084         TreeItem::Type type = ti->getType();
6085         if (type == TreeItem::Branch || type == TreeItem::MapCenter)
6086             return (BranchItem *)ti;
6087     }
6088     return NULL;
6089 }
6090
6091 QList<BranchItem *> VymModel::getSelectedBranches()
6092 {
6093     QList<BranchItem *> bis;
6094     foreach (TreeItem *ti, getSelectedItems()) {
6095         TreeItem::Type type = ti->getType();
6096         if (type == TreeItem::Branch || type == TreeItem::MapCenter)
6097             bis.append((BranchItem *)ti);
6098     }
6099     return bis;
6100 }
6101
6102 ImageItem *VymModel::getSelectedImage()
6103 {
6104     TreeItem *ti = getSelectedItem();
6105     if (ti && ti->getType() == TreeItem::Image)
6106         return (ImageItem *)ti;
6107     else
6108         return NULL;
6109 }
6110
6111 Task *VymModel::getSelectedTask()
6112 {
6113     BranchItem *selbi = getSelectedBranch();
6114     if (selbi)
6115         return selbi->getTask();
6116     else
6117         return NULL;
6118 }
6119
6120 Link *VymModel::getSelectedXLink()
6121 {
6122     XLinkItem *xli = getSelectedXLinkItem();
6123     if (xli)
6124         return xli->getLink();
6125     return NULL;
6126 }
6127
6128 XLinkItem *VymModel::getSelectedXLinkItem()
6129 {
6130     TreeItem *ti = getSelectedItem();
6131     if (ti && ti->getType() == TreeItem::XLink)
6132         return (XLinkItem *)ti;
6133     else
6134         return NULL;
6135 }
6136
6137 AttributeItem *VymModel::getSelectedAttribute()
6138 {
6139     TreeItem *ti = getSelectedItem();
6140     if (ti && ti->getType() == TreeItem::Attribute)
6141         return (AttributeItem *)ti;
6142     else
6143         return NULL;
6144 }
6145
6146 TreeItem *VymModel::getSelectedItem()
6147 {
6148     if (!selModel)
6149         return NULL;
6150     QModelIndexList list = selModel->selectedIndexes();
6151     if (list.count() == 1)
6152         return getItem(list.first());
6153     else
6154         return NULL;
6155 }
6156
6157 QList<TreeItem *> VymModel::getSelectedItems()
6158 {
6159     QList<TreeItem *> l;
6160     if (!selModel)
6161         return l;
6162     QModelIndexList list = selModel->selectedIndexes();
6163     foreach (QModelIndex ix, list)
6164         l.append(getItem(ix));
6165     return l;
6166 }
6167
6168 QModelIndex VymModel::getSelectedIndex()
6169 {
6170     QModelIndexList list = selModel->selectedIndexes();
6171     if (list.count() == 1)
6172         return list.first();
6173     else
6174         return QModelIndex();
6175 }
6176
6177 QList<uint> VymModel::getSelectedIDs()
6178 {
6179     QList<uint> uids;
6180     foreach (TreeItem *ti, getSelectedItems())
6181         uids.append(ti->getID());
6182     return uids;
6183 }
6184
6185 QStringList VymModel::getSelectedUUIDs()
6186 {
6187     QStringList uids;
6188     foreach (TreeItem *ti, getSelectedItems())
6189         uids.append(ti->getUuid().toString());
6190     return uids;
6191 }
6192
6193 bool VymModel::isSelected(TreeItem *ti)
6194 {
6195     return getSelectedItems().contains(ti);
6196 }
6197
6198 QString VymModel::getSelectString()
6199 {
6200     return getSelectString(getSelectedItem());
6201 }
6202
6203 QString VymModel::getSelectString(
6204     LinkableMapObj *lmo) // only for convenience. Used in MapEditor
6205 {
6206     if (!lmo)
6207         return QString();
6208     return getSelectString(lmo->getTreeItem());
6209 }
6210
6211 QString VymModel::getSelectString(TreeItem *ti)
6212 {
6213     QString s;
6214     if (!ti || ti->depth() < 0)
6215         return s;
6216     switch (ti->getType()) {
6217     case TreeItem::MapCenter:
6218         s = "mc:";
6219         break;
6220     case TreeItem::Branch:
6221         s = "bo:";
6222         break;
6223     case TreeItem::Image:
6224         s = "fi:";
6225         break;
6226     case TreeItem::Attribute:
6227         s = "ai:";
6228         break;
6229     case TreeItem::XLink:
6230         s = "xl:";
6231         break;
6232     default:
6233         s = "unknown type in VymModel::getSelectString()";
6234         break;
6235     }
6236     s = s + QString("%1").arg(ti->num());
6237     if (ti->depth() > 0)
6238         // call myself recursively
6239         s = getSelectString(ti->parent()) + "," + s;
6240     return s;
6241 }
6242
6243 QString VymModel::getSelectString(BranchItem *bi)
6244 {
6245     return getSelectString((TreeItem *)bi);
6246 }
6247
6248 QString VymModel::getSelectString(const uint &i)
6249 {
6250     return getSelectString(findID(i));
6251 }
6252
6253 void VymModel::setLatestAddedItem(TreeItem *ti) { latestAddedItem = ti; }
6254
6255 TreeItem *VymModel::getLatestAddedItem() { return latestAddedItem; }
6256
6257 SlideModel *VymModel::getSlideModel() { return slideModel; }
6258
6259 int VymModel::slideCount() { return slideModel->count(); }
6260
6261 SlideItem *VymModel::addSlide()
6262 {
6263     SlideItem *si = slideModel->getSelectedItem();
6264     if (si)
6265         si = slideModel->addSlide(NULL, si->childNumber() + 1);
6266     else
6267         si = slideModel->addSlide();
6268
6269     TreeItem *seli = getSelectedItem();
6270
6271     if (si && seli) {
6272         QString inScript;
6273         if (!loadStringFromDisk(vymBaseDir.path() +
6274                                     "/macros/slideeditor-snapshot.vys",
6275                                 inScript)) {
6276             qWarning() << "VymModel::addSlide couldn't load template for "
6277                           "taking snapshot";
6278             return NULL;
6279         }
6280
6281         inScript.replace(
6282             "CURRENT_ZOOM",
6283             QString().setNum(getMapEditor()->getZoomFactorTarget()));
6284         inScript.replace("CURRENT_ANGLE",
6285                          QString().setNum(getMapEditor()->getAngleTarget()));
6286         inScript.replace("CURRENT_ID",
6287                          "\"" + seli->getUuid().toString() + "\"");
6288
6289         si->setInScript(inScript);
6290         slideModel->setData(slideModel->index(si), seli->getHeadingPlain());
6291     }
6292     QString s = "<vymmap>" + si->saveToDir() + "</vymmap>";
6293     int pos = si->childNumber();
6294     saveState(PartOfMap, getSelectString(),
6295               QString("removeSlide (%1)").arg(pos), getSelectString(),
6296               QString("addMapInsert (\"PATH\",%1)").arg(pos), "Add slide", NULL,
6297               s);
6298     return si;
6299 }
6300
6301 void VymModel::deleteSlide(SlideItem *si)
6302 {
6303     if (si) {
6304         QString s = "<vymmap>" + si->saveToDir() + "</vymmap>";
6305         int pos = si->childNumber();
6306         saveState(PartOfMap, getSelectString(),
6307                   QString("addMapInsert (\"PATH\",%1)").arg(pos),
6308                   getSelectString(), QString("removeSlide (%1)").arg(pos),
6309                   "Remove slide", NULL, s);
6310         slideModel->deleteSlide(si);
6311     }
6312 }
6313
6314 void VymModel::deleteSlide(int n) { deleteSlide(slideModel->getSlide(n)); }
6315
6316 void VymModel::relinkSlide(SlideItem *si, int pos)
6317 {
6318     if (si && pos >= 0)
6319         slideModel->relinkSlide(si, si->parent(), pos);
6320 }
6321
6322 bool VymModel::moveSlideDown(int n)
6323 {
6324     SlideItem *si = NULL;
6325     if (n < 0) // default if called without parameters
6326     {
6327         si = slideModel->getSelectedItem();
6328         if (si)
6329             n = si->childNumber();
6330         else
6331             return false;
6332     }
6333     else
6334         si = slideModel->getSlide(n);
6335     if (si && n >= 0 && n < slideModel->count() - 1) {
6336         blockSlideSelection = true;
6337         slideModel->relinkSlide(si, si->parent(), n + 1);
6338         blockSlideSelection = false;
6339         saveState(getSelectString(), QString("moveSlideUp (%1)").arg(n + 1),
6340                   getSelectString(), QString("moveSlideDown (%1)").arg(n),
6341                   QString("Move slide %1 down").arg(n));
6342         return true;
6343     }
6344     else
6345         return false;
6346 }
6347
6348 bool VymModel::moveSlideUp(int n)
6349 {
6350     SlideItem *si = NULL;
6351     if (n < 0) // default if called without parameters
6352     {
6353         si = slideModel->getSelectedItem();
6354         if (si)
6355             n = si->childNumber();
6356         else
6357             return false;
6358     }
6359     else
6360         si = slideModel->getSlide(n);
6361     if (si && n > 0 && n < slideModel->count()) {
6362         blockSlideSelection = true;
6363         slideModel->relinkSlide(si, si->parent(), n - 1);
6364         blockSlideSelection = false;
6365         saveState(getSelectString(), QString("moveSlideDown (%1)").arg(n - 1),
6366                   getSelectString(), QString("moveSlideUp (%1)").arg(n),
6367                   QString("Move slide %1 up").arg(n));
6368         return true;
6369     }
6370     else
6371         return false;
6372 }
6373
6374 void VymModel::updateSlideSelection(QItemSelection newsel, QItemSelection)
6375 {
6376     if (blockSlideSelection)
6377         return;
6378     QModelIndex ix;
6379     foreach (ix, newsel.indexes()) {
6380         SlideItem *si = static_cast<SlideItem *>(ix.internalPointer());
6381         QString inScript = si->getInScript();
6382
6383         // show inScript in ScriptEditor
6384         scriptEditor->setSlideScript(modelID, si->getID(), inScript);
6385
6386         // Execute inScript
6387         execute(inScript);
6388     }
6389 }