]> git.sven.stormbind.net Git - sven/vym.git/blobdiff - src/vymmodel.cpp
New upstream version 2.9.22
[sven/vym.git] / src / vymmodel.cpp
diff --git a/src/vymmodel.cpp b/src/vymmodel.cpp
new file mode 100644 (file)
index 0000000..d3ce384
--- /dev/null
@@ -0,0 +1,6371 @@
+#include <QApplication>
+#include <QSvgGenerator>
+
+#if defined(VYM_DBUS)
+#include <QtDBus/QDBusConnection>
+#endif
+
+#ifndef Q_OS_WINDOWS
+#include <unistd.h>
+#else
+#define sleep Sleep
+#endif
+
+#include <QColorDialog>
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QPrinter>
+
+#include "vymmodel.h"
+
+#include "attributeitem.h"
+#include "branchitem.h"
+#include "confluence-agent.h"
+#include "download-agent.h"
+#include "editxlinkdialog.h"
+#include "export-ao.h"
+#include "export-ascii.h"
+#include "export-confluence.h"
+#include "export-csv.h"
+#include "export-firefox.h"
+#include "export-html.h"
+#include "export-impress.h"
+#include "export-latex.h"
+#include "export-markdown.h"
+#include "export-orgmode.h"
+#include "file.h"
+#include "findresultmodel.h"
+#include "jira-agent.h"
+#include "lockedfiledialog.h"
+#include "mainwindow.h"
+#include "misc.h"
+#include "noteeditor.h"
+#include "options.h"
+#include "scripteditor.h"
+#include "slideitem.h"
+#include "slidemodel.h"
+#include "taskeditor.h"
+#include "taskmodel.h"
+#include "treeitem.h"
+#include "vymprocess.h"
+#include "warningdialog.h"
+#include "xlinkitem.h"
+#include "xlinkobj.h"
+#include "xml-freemind.h"
+#include "xml-vym.h"
+#include "xmlobj.h"
+
+#ifdef Q_OS_WINDOWS
+#include <windows.h>
+#endif
+
+extern bool debug;
+extern bool testmode;
+extern bool restoreMode;
+extern QStringList ignoredLockedFiles;
+
+extern Main *mainWindow;
+
+extern QDir tmpVymDir;
+
+extern NoteEditor *noteEditor;
+extern TaskEditor *taskEditor;
+extern ScriptEditor *scriptEditor;
+extern FlagRowMaster *standardFlagsMaster;
+extern FlagRowMaster *userFlagsMaster;
+
+extern Options options;
+
+extern QString clipboardDir;
+extern QString clipboardFile;
+
+extern ImageIO imageIO;
+
+extern TaskModel *taskModel;
+
+extern QString vymName;
+extern QString vymVersion;
+extern QDir vymBaseDir;
+
+extern QDir lastImageDir;
+extern QDir lastMapDir;
+extern QDir lastExportDir;
+
+extern Settings settings;
+extern QTextStream vout;
+
+uint VymModel::idLast = 0; // make instance
+
+VymModel::VymModel()
+{
+    // qDebug()<< "Const VymModel" << this;
+    init();
+    rootItem->setModel(this);
+    wrapper = new VymModelWrapper(this);
+}
+
+VymModel::~VymModel()
+{
+    // out << "Destr VymModel begin this="<<this<<"  "<<mapName<<flush;
+    mapEditor = nullptr;
+    repositionBlocked = true;
+    autosaveTimer->stop();
+    fileChangedTimer->stop();
+    stopAllAnimation();
+
+    // qApp->processEvents();  // Update view (scene()->update() is not enough)
+    // qDebug() << "Destr VymModel end   this="<<this;
+
+    vymLock.releaseLock();
+
+    delete (wrapper);
+}
+
+void VymModel::clear()
+{
+    while (rootItem->childCount() > 0) {
+        // qDebug()<<"VM::clear  ri="<<rootItem<<"
+        // ri->count()="<<rootItem->childCount();
+        deleteItem(rootItem->getChildNum(0));
+    }
+}
+
+void VymModel::init()
+{
+    // No MapEditor yet
+    mapEditor = NULL;
+
+    // Use default author
+    author =
+        settings
+            .value("/user/name", tr("unknown user",
+                                    "default name for map author in settings"))
+            .toString();
+
+    // States and IDs
+    idLast++;
+    modelID = idLast;
+    mapChanged = false;
+    mapDefault = true;
+    mapUnsaved = false;
+
+    // Selection history
+    selModel = NULL;
+    selectionBlocked = false;
+    resetSelectionHistory();
+
+    resetHistory();
+
+    // Create tmp dirs
+    makeTmpDirectories();
+
+    // Files
+    readonly = false;
+    zipped = true;
+    filePath = "";
+    fileName = tr("unnamed");
+    mapName = fileName;
+    repositionBlocked = false;
+    saveStateBlocked = false;
+
+    autosaveTimer = new QTimer(this);
+    connect(autosaveTimer, SIGNAL(timeout()), this, SLOT(autosave()));
+
+    fileChangedTimer = new QTimer(this);
+    connect(fileChangedTimer, SIGNAL(timeout()), this, SLOT(fileChanged()));
+    fileChangedTimer->start(3000);
+
+    taskAlarmTimer = new QTimer(this);
+    connect(taskAlarmTimer, SIGNAL(timeout()), this, SLOT(updateTasksAlarm()));
+    taskAlarmTimer->start(3000);
+
+    // animations   // FIXME-4 switch to new animation system
+    animationUse =
+        settings.value("/animation/use", false)
+            .toBool(); // FIXME-4 add options to control _what_ is animated
+    animationTicks = settings.value("/animation/ticks", 20).toInt();
+    animationInterval = settings.value("/animation/interval", 5).toInt();
+    animObjList.clear();
+    animationTimer = new QTimer(this);
+    connect(animationTimer, SIGNAL(timeout()), this, SLOT(animate()));
+
+    // View - map
+    defaultFont.setPointSizeF(16);
+    defLinkColor = QColor(0, 0, 255);
+    linkcolorhint = LinkableMapObj::DefaultColor;
+    linkstyle = LinkableMapObj::PolyParabel;
+    defXLinkPen.setWidth(1);
+    defXLinkPen.setColor(QColor(50, 50, 255));
+    defXLinkPen.setStyle(Qt::DashLine);
+    defXLinkStyleBegin = "HeadFull";
+    defXLinkStyleEnd = "HeadFull";
+
+    hasContextPos = false;
+
+    hidemode = TreeItem::HideNone;
+
+    // Animation in MapEditor
+    zoomFactor = 1;
+    rotationAngle = 0;
+    animDuration = 2000;
+    animCurve = QEasingCurve::OutQuint;
+
+    // Initialize presentation slides
+    slideModel = new SlideModel(this);
+    blockSlideSelection = false;
+
+    // Avoid recursions later
+    cleaningUpLinks = false;
+
+    // Network
+    netstate = Offline;
+
+#if defined(VYM_DBUS)
+    // Announce myself on DBUS
+    new AdaptorModel(this); // Created and not deleted as documented in Qt
+    if (!QDBusConnection::sessionBus().registerObject(
+            QString("/vymmodel_%1").arg(modelID), this))
+        qWarning("VymModel: Couldn't register DBUS object!");
+#endif
+}
+
+void VymModel::makeTmpDirectories()
+{
+    // Create unique temporary directories
+    tmpMapDirPath = tmpVymDir.path() + QString("/model-%1").arg(modelID);
+    histPath = tmpMapDirPath + "/history";
+    QDir d;
+    d.mkdir(tmpMapDirPath);
+}
+
+QString VymModel::tmpDirPath() { return tmpMapDirPath; }
+
+MapEditor *VymModel::getMapEditor() { return mapEditor; }
+
+VymModelWrapper *VymModel::getWrapper() { return wrapper; }
+
+bool VymModel::isRepositionBlocked() { return repositionBlocked; }
+
+void VymModel::updateActions()
+{
+    // Tell mainwindow to update states of actions
+    mainWindow->updateActions();
+}
+
+bool VymModel::setData(const QModelIndex &, const QVariant &value, int role)
+{
+    if (role != Qt::EditRole)
+        return false;
+
+    setHeadingPlainText(value.toString());
+
+    return true;
+}
+
+void VymModel::resetUsedFlags()
+{
+    standardFlagsMaster->resetUsedCounter();
+    userFlagsMaster->resetUsedCounter();
+}
+
+QString VymModel::saveToDir(const QString &tmpdir, const QString &prefix,
+                            FlagRowMaster::WriteMode flagMode, const QPointF &offset,
+                            TreeItem *saveSel)
+{
+    // tmpdir      temporary directory to which data will be written
+    // prefix      mapname, which will be appended to images etc.
+    //
+    // writeflags   Only write flags for "real" save of map, not undo
+    // offset      offset of bbox of whole map in scene.
+    //             Needed for XML export
+
+    XMLObj xml;
+
+    // Save Header
+    QString ls;
+    switch (linkstyle) {
+    case LinkableMapObj::Line:
+        ls = "StyleLine";
+        break;
+    case LinkableMapObj::Parabel:
+        ls = "StyleParabel";
+        break;
+    case LinkableMapObj::PolyLine:
+        ls = "StylePolyLine";
+        break;
+    default:
+        ls = "StylePolyParabel";
+        break;
+    }
+
+    QString header =
+        "<?xml version=\"1.0\" encoding=\"utf-8\"?><!DOCTYPE vymmap>\n";
+    QString colhint = "";
+    if (linkcolorhint == LinkableMapObj::HeadingColor)
+        colhint = xml.attribut("linkColorHint", "HeadingColor");
+
+    QString mapAttr = xml.attribut("version", vymVersion);
+    if (!saveSel) {
+        QPen selPen = mapEditor->getSelectionPen();
+        QBrush selBrush = mapEditor->getSelectionBrush();
+
+        mapAttr +=
+            xml.attribut("author", author) + xml.attribut("title", title) +
+            xml.attribut("comment", comment) + xml.attribut("date", getDate()) +
+            xml.attribut("branchCount", QString().number(branchCount())) +
+            xml.attribut(
+                "backgroundColor",
+                mapEditor->getScene()->backgroundBrush().color().name()) +
+            xml.attribut("defaultFont", defaultFont.toString()) +
+            xml.attribut("selectionColor",  // FIXME-2 Only for compatibility until 2.9.513
+                         selBrush.color().name(QColor::HexArgb)) +
+            xml.attribut("selectionPenColor", selPen.color().name(QColor::HexArgb)) +
+            xml.attribut("selectionPenWidth", 
+                         QString().setNum(selPen.width())) + 
+            xml.attribut("selectionBrushColor", selBrush.color().name(QColor::HexArgb)) +
+            xml.attribut("linkStyle", ls) +
+            xml.attribut("linkColor", defLinkColor.name()) +
+            xml.attribut("defXLinkColor", defXLinkPen.color().name()) +
+            xml.attribut("defXLinkWidth",
+                         QString().setNum(defXLinkPen.width(), 10)) +
+            xml.attribut("defXLinkPenStyle",
+                         penStyleToString(defXLinkPen.style())) +
+            xml.attribut("defXLinkStyleBegin", defXLinkStyleBegin) +
+            xml.attribut("defXLinkStyleEnd", defXLinkStyleEnd) +
+            xml.attribut("mapZoomFactor",
+                         QString().setNum(mapEditor->getZoomFactorTarget())) +
+            xml.attribut("mapRotationAngle",
+                         QString().setNum(mapEditor->getAngleTarget())) +
+            colhint;
+    }
+    header += xml.beginElement("vymmap", mapAttr);
+    xml.incIndent();
+
+    // Find the used flags while traversing the tree
+    resetUsedFlags();
+
+    // Temporary list of links
+    QList<Link *> tmpLinks;
+
+    QString tree;
+    // Build xml recursivly
+    if (!saveSel) {
+        // Save all mapcenters as complete map, if saveSel not set
+        tree += saveTreeToDir(tmpdir, prefix, offset, tmpLinks);
+
+        // Save local settings
+        tree += settings.getDataXML(destPath);
+
+        // Save selection
+        if (getSelectedItem() && !saveSel)
+            tree += xml.valueElement("select", getSelectString());
+    }
+    else {
+        switch (saveSel->getType()) {
+        case TreeItem::Branch:
+            // Save Subtree
+            tree += ((BranchItem *)saveSel)
+                        ->saveToDir(tmpdir, prefix, offset, tmpLinks);
+            break;
+        case TreeItem::MapCenter:
+            // Save Subtree
+            tree += ((BranchItem *)saveSel)
+                        ->saveToDir(tmpdir, prefix, offset, tmpLinks);
+            break;
+        case TreeItem::Image:
+            // Save Image
+            tree += ((ImageItem *)saveSel)->saveToDir(tmpdir, prefix);
+            break;
+        default:
+            // other types shouldn't be safed directly...
+            break;
+        }
+    }
+
+    QString flags;
+
+    // Write images and definitions of used user flags
+    if (flagMode != FlagRowMaster::NoFlags) {
+        // First find out, which flags are used
+        // Definitions
+        flags += userFlagsMaster->saveDef(flagMode);
+
+        userFlagsMaster->saveDataToDir(tmpdir + "flags/user/", flagMode);
+        standardFlagsMaster->saveDataToDir(tmpdir + "flags/standard/",
+                                           flagMode);
+    }
+
+    QString footer;
+    // Save XLinks
+    for (int i = 0; i < tmpLinks.count(); ++i)
+        footer += tmpLinks.at(i)->saveToDir();
+
+    // Save slides
+    footer += slideModel->saveToDir();
+
+    xml.decIndent();
+    footer += xml.endElement("vymmap");
+
+    return header + flags + tree + footer;
+}
+
+QString VymModel::saveTreeToDir(const QString &tmpdir, const QString &prefix,
+                                const QPointF &offset, QList<Link *> &tmpLinks)
+{
+    QString s;
+    for (int i = 0; i < rootItem->branchCount(); i++)
+        s += rootItem->getBranchNum(i)->saveToDir(tmpdir, prefix, offset,
+                                                  tmpLinks);
+    return s;
+}
+
+void VymModel::setFilePath(QString fpath, QString destname)
+{
+    if (fpath.isEmpty() || fpath == "") {
+        filePath = "";
+        fileName = "";
+        destPath = "";
+    }
+    else {
+        filePath = fpath;    // becomes absolute path
+        fileName = fpath;    // gets stripped of path
+        destPath = destname; // needed for vymlinks and during load to reset
+                             // fileChangedTime
+
+        // If fpath is not an absolute path, complete it
+        filePath = QDir(fpath).absolutePath();
+        fileDir = filePath.left(1 + filePath.lastIndexOf("/"));
+
+        // Set short name, too. Search from behind:
+        fileName = basename(fileName);
+
+        // Forget the .vym (or .xml) for name of map
+        mapName =
+            fileName.left(fileName.lastIndexOf(".", -1, Qt::CaseSensitive));
+    }
+}
+
+void VymModel::setFilePath(QString fpath) { setFilePath(fpath, fpath); }
+
+QString VymModel::getFileDir() { return fileDir; }
+
+QString VymModel::getFilePath() { return filePath; }
+
+QString VymModel::getFileName() { return fileName; }
+
+QString VymModel::getMapName() { return mapName; }
+
+QString VymModel::getDestPath() { return destPath; }
+
+bool VymModel::parseVymText(const QString &s)
+{
+    bool ok = false;
+    BranchItem *bi = getSelectedBranch();
+    if (bi) {
+        parseBaseHandler *handler = new parseVYMHandler;
+
+        bool saveStateBlockedOrg = saveStateBlocked;
+        repositionBlocked = true;
+        saveStateBlocked = true;
+        QXmlInputSource source;
+        source.setData(s);
+        QXmlSimpleReader reader;
+        reader.setContentHandler(handler);
+        reader.setErrorHandler(handler);
+
+        handler->setInputString(s);
+        handler->setModel(this);
+        handler->setLoadMode(ImportReplace, 0);
+
+        ok = reader.parse(source);
+        repositionBlocked = false;
+        saveStateBlocked = saveStateBlockedOrg;
+        if (ok) {
+            if (s.startsWith("<vymnote"))
+                emitNoteChanged(bi);
+            emitDataChanged(bi);
+            reposition(); // to generate bbox sizes
+        }
+        else {
+            QMessageBox::critical(0, tr("Critical Parse Error"),
+                                  tr(handler->errorProtocol().toUtf8()));
+            // returnCode=1;
+            // Still return "success": the map maybe at least
+            // partially read by the parser
+        }
+    }
+    return ok;
+}
+
+File::ErrorCode VymModel::loadMap(QString fname, const LoadMode &lmode,
+                                  const FileType &ftype,
+                                  const int &contentFilter, int pos)
+{
+    File::ErrorCode err = File::Success;
+
+    // Get updated zoomFactor, before applying one read from file in the end
+    if (mapEditor) {
+        zoomFactor = mapEditor->getZoomFactorTarget();
+        rotationAngle = mapEditor->getAngleTarget();
+    }
+
+    parseBaseHandler *handler;
+    fileType = ftype;
+    switch (fileType) {
+    case VymMap:
+        handler = new parseVYMHandler;
+        ((parseVYMHandler *)handler)->setContentFilter(contentFilter);
+        break;
+    case FreemindMap:
+        handler = new parseFreemindHandler;
+        break;
+    default:
+        QMessageBox::critical(0, tr("Critical Parse Error"),
+                              "Unknown FileType in VymModel::load()");
+        return File::Aborted;
+    }
+
+    if (lmode == NewMap) {
+        // Reset timestamp to check for later updates of file
+        fileChangedTime = QFileInfo(destPath).lastModified();
+
+        selModel->clearSelection();
+    }
+
+    bool zipped_org = zipped;
+
+    // Create temporary directory for packing
+    bool ok;
+    QString tmpZipDir = makeTmpDir(ok, tmpDirPath(), "unzip");
+    if (!ok) {
+        QMessageBox::critical(
+            0, tr("Critical Load Error"),
+            tr("Couldn't create temporary directory before load\n"));
+        return File::Aborted;
+    }
+
+    QString xmlfile;
+    if (fname.right(4) == ".xml" || fname.right(3) == ".mm") {
+        xmlfile = fname;
+        zipped = false;
+
+        if (lmode == NewMap || lmode == DefaultMap)
+            zipped_org = false;
+    }
+    else {
+        // Try to unzip file
+        err = unzipDir(tmpZipDir, fname);
+    }
+
+    if (zipped) {
+        // Look for mapname.xml
+        xmlfile = fname.left(fname.lastIndexOf(".", -1, Qt::CaseSensitive));
+        xmlfile = xmlfile.section('/', -1);
+        QFile mfile(tmpZipDir + "/" + xmlfile + ".xml");
+        if (!mfile.exists()) {
+            // mapname.xml does not exist, well,
+            // maybe someone renamed the mapname.vym file...
+            // Try to find any .xml in the toplevel
+            // directory of the .vym file
+            QStringList filters;
+            filters << "*.xml";
+            QStringList flist = QDir(tmpZipDir).entryList(filters);
+            if (flist.count() == 1) {
+                // Only one entry, take this one
+                xmlfile = tmpZipDir + "/" + flist.first();
+            }
+            else {
+                for (QStringList::Iterator it = flist.begin();
+                     it != flist.end(); ++it)
+                    *it = tmpZipDir + "/" + *it;
+                // FIXME-4 Multiple entries, load all (but only the first one
+                // into this ME)
+                // mainWindow->fileLoadFromTmp (flist);
+                // returnCode = 1;     // Silently forget this attempt to load
+                qWarning("MainWindow::load (fn)  multimap found...");
+            }
+
+            if (flist.isEmpty()) {
+                QMessageBox::critical(
+                    0, tr("Critical Load Error"),
+                    tr("Couldn't find a map (*.xml) in .vym archive.\n"));
+                err = File::Aborted;
+            }
+        } // file doesn't exist
+        else
+            xmlfile = mfile.fileName();
+    }
+
+    QFile file(xmlfile);
+
+    // I am paranoid: file should exist anyway
+    // according to check in mainwindow.
+    if (!file.exists()) {
+        QMessageBox::critical(
+            0, tr("Critical Parse Error"),
+            tr(QString("Couldn't open map %1").arg(file.fileName()).toUtf8()));
+        err = File::Aborted;
+    }
+    else {
+        bool saveStateBlockedOrg = saveStateBlocked;
+        repositionBlocked = true;
+        saveStateBlocked = true;
+        mapEditor->setViewportUpdateMode(QGraphicsView::NoViewportUpdate);
+        QXmlInputSource source(&file);
+        QXmlSimpleReader reader;
+        reader.setContentHandler(handler);
+        reader.setErrorHandler(handler);
+        handler->setModel(this);
+
+        // We need to set the tmpDir in order  to load files with rel. path
+        QString tmpdir;
+        if (zipped)
+            tmpdir = tmpZipDir;
+        else
+            tmpdir = fname.left(fname.lastIndexOf("/", -1));
+        handler->setTmpDir(tmpdir);
+        handler->setInputFile(file.fileName());
+        if (lmode == ImportReplace)
+            handler->setLoadMode(ImportReplace, pos);
+        else
+            handler->setLoadMode(lmode, pos);
+
+        // Here we actually parse the XML file
+        bool ok = reader.parse(source);
+
+        // Aftermath
+        repositionBlocked = false;
+        saveStateBlocked = saveStateBlockedOrg;
+        mapEditor->setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate);
+        file.close();
+        if (ok) {
+            reposition(); // to generate bbox sizes
+            emitSelectionChanged();
+
+            if (lmode == NewMap) // no lockfile for default map!
+            {
+                mapDefault = false;
+                mapChanged = false;
+                mapUnsaved = false;
+                autosaveTimer->stop();
+
+                resetHistory();
+                resetSelectionHistory();
+
+                // Set treeEditor and slideEditor visibilty per map
+                vymView->readSettings();
+
+                if (!tryVymLock() && debug)
+                    qWarning() << "VM::loadMap  no lockfile created!";
+            }
+
+            // Recalc priorities and sort
+            taskModel->recalcPriorities();
+        }
+        else {
+            QMessageBox::critical(0, tr("Critical Parse Error"),
+                                  tr(handler->errorProtocol().toUtf8()));
+            // returnCode=1;
+            // Still return "success": the map maybe at least
+            // partially read by the parser
+        }
+    }
+
+    // Delete tmpZipDir
+    removeDir(QDir(tmpZipDir));
+
+    // Restore original zip state
+    zipped = zipped_org;
+
+    updateActions();
+
+    if (lmode != NewMap)
+        emitUpdateQueries();
+
+    if (mapEditor) {
+        mapEditor->setZoomFactorTarget(zoomFactor);
+        mapEditor->setAngleTarget(rotationAngle);
+    }
+
+    qApp->processEvents(); // Update view (scene()->update() is not enough)
+    return err;
+}
+
+File::ErrorCode VymModel::save(const SaveMode &savemode)
+{
+    QString tmpZipDir;
+    QString mapFileName;
+    QString saveFilePath;
+
+    File::ErrorCode err = File::Success;
+
+    if (zipped)
+        // save as .xml
+        mapFileName = mapName + ".xml";
+    else
+        // use name given by user, could be anything
+        mapFileName = fileName;
+
+    // Look, if we should zip the data:
+    if (!zipped)
+    {
+        QMessageBox mb(vymName,
+                       tr("The map %1\ndid not use the compressed "
+                          "vym file format.\nWriting it uncompressed will also "
+                          "write images \n"
+                          "and flags and thus may overwrite files into the "
+                          "given directory\n\nDo you want to write the map")
+                           .arg(filePath),
+                       QMessageBox::Warning,
+                       QMessageBox::Yes | QMessageBox::Default, QMessageBox::No,
+                       QMessageBox::Cancel | QMessageBox::Escape);
+        mb.setButtonText(QMessageBox::Yes, tr("compressed (vym default)"));
+        mb.setButtonText(
+            QMessageBox::No,
+            tr("uncompressed, potentially overwrite existing data"));
+        mb.setButtonText(QMessageBox::Cancel, tr("Cancel"));
+        switch (mb.exec()) {
+            case QMessageBox::Yes:
+                // save compressed (default file format)
+                zipped = true;
+                break;
+            case QMessageBox::No:
+                // save uncompressed
+                zipped = false;
+                break;
+            case QMessageBox::Cancel:
+                // do nothing
+                return File::Aborted;
+                break;
+        }
+    }
+
+    // First backup existing file, we
+    // don't want to add to old zip archives
+    QFile f(destPath);
+    if (f.exists()) {
+        if (settings.value("/system/writeBackupFile").toBool()) {
+            QString backupFileName(destPath + "~");
+            QFile backupFile(backupFileName);
+            if (backupFile.exists() && !backupFile.remove()) {
+                QMessageBox::warning(
+                    0, tr("Save Error"),
+                    tr("%1\ncould not be removed before saving")
+                        .arg(backupFileName));
+            }
+            else {
+                if (!f.rename(backupFileName)) {
+                    QMessageBox::warning(
+                        0, tr("Save Error"),
+                        tr("%1\ncould not be renamed before saving")
+                            .arg(destPath));
+                }
+            }
+        }
+    }
+
+    if (zipped) {
+        // Create temporary directory for packing
+        bool ok;
+        tmpZipDir = makeTmpDir(ok, tmpDirPath(), "zip");
+        if (!ok) {
+            QMessageBox::critical(
+                0, tr("Critical Save Error"),
+                tr("Couldn't create temporary directory before save\n"));
+            return File::Aborted;
+        }
+
+        saveFilePath = filePath;
+        setFilePath(tmpZipDir + "/" + mapName + ".xml", saveFilePath);
+    } // zipped
+
+    // Create mapName and fileDir
+    makeSubDirs(fileDir);
+
+    QString saveFile;
+    if (savemode == CompleteMap || selModel->selection().isEmpty()) {
+        // Save complete map
+        if (zipped)
+            // Use defined name for map within zipfile to avoid problems
+            // with zip library and umlauts (see #98)
+            saveFile =
+                saveToDir(fileDir, "", FlagRowMaster::UsedFlags, QPointF(), NULL);
+        else
+            saveFile = saveToDir(fileDir, mapName + "-", FlagRowMaster::UsedFlags,
+                                 QPointF(), NULL);
+        mapChanged = false;
+        mapUnsaved = false;
+        autosaveTimer->stop();
+    }
+    else {
+        // Save part of map
+        if (selectionType() == TreeItem::Image)
+            saveImage();
+        else
+            saveFile = saveToDir(fileDir, mapName + "-", FlagRowMaster::UsedFlags,
+                                 QPointF(), getSelectedBranch());
+        // FIXME-3 take care of multiselections when saving parts
+    }
+
+    bool saved;
+    if (zipped)
+        // Use defined map name "map.xml", if zipped. Introduce in 2.6.6
+        saved = saveStringToDisk(fileDir + "map.xml", saveFile);
+    else
+        // Use regular mapName, when saved as XML
+        saved = saveStringToDisk(fileDir + mapFileName, saveFile);
+    if (!saved) {
+        err = File::Aborted;
+        qWarning("ME::saveStringToDisk failed!");
+    }
+
+    if (zipped) {
+        // zip
+        if (err == File::Success)
+            err = zipDir(tmpZipDir, destPath);
+
+        // Delete tmpDir
+        removeDir(QDir(tmpZipDir));
+
+        // Restore original filepath outside of tmp zip dir
+        setFilePath(saveFilePath);
+    }
+
+    updateActions();
+
+    fileChangedTime = QFileInfo(destPath).lastModified();
+    return err;
+}
+
+ImageItem* VymModel::loadImage(BranchItem *dst, const QString &fn)  // FIXME-2 better move filedialog to MainWindow
+{
+    if (!dst)
+        dst = getSelectedBranch();
+    if (dst) {
+        QString filter = QString(tr("Images") +
+                                 " (*.png *.bmp *.xbm *.jpg *.png *.xpm *.gif "
+                                 "*.pnm *.svg *.svgz);;" +
+                                 tr("All", "Filedialog") + " (*.*)");
+        QStringList fns;
+        if (fn.isEmpty())
+            fns = QFileDialog::getOpenFileNames(
+                NULL, vymName + " - " + tr("Load image"), lastImageDir.path(),
+                filter);
+        else
+            fns.append(fn);
+
+        if (!fns.isEmpty()) {
+            lastImageDir.setPath(
+                fns.first().left(fns.first().lastIndexOf("/")));
+            QString s;
+            for (int j = 0; j < fns.count(); j++) {
+                s = fns.at(j);
+                ImageItem *ii = createImage(dst);
+                if (ii && ii->load(s)) {
+                    saveState((TreeItem *)ii, "remove()", dst,
+                              QString("loadImage (\"%1\")").arg(s),
+                              QString("Add image %1 to %2")
+                                  .arg(s)
+                                  .arg(getObjectName(dst)));
+                    // Find nice position for new image, take childPos // FIXME-1 position below last image
+                    FloatImageObj *fio = (FloatImageObj *)(ii->getMO());
+                    if (fio) {
+                        LinkableMapObj *parLMO = dst->getLMO();
+
+                        if (parLMO) {
+                            fio->move(parLMO->getChildRefPos());
+                            fio->setRelPos();
+                        }
+                    }
+
+                    // On default include image // FIXME-4 check, if we change
+                    // default settings...
+                    select(dst);
+                    setIncludeImagesHor(false);
+                    setIncludeImagesVer(true);
+
+                    reposition();
+                    return ii;
+                }
+                else {
+                    qWarning() << "vymmodel: Failed to load " + s;
+                    deleteItem(ii);
+                }
+            }
+        }
+    }
+    return nullptr;
+}
+
+void VymModel::saveImage(ImageItem *ii, QString fn)
+{
+    if (!ii)
+        ii = getSelectedImage();
+    if (ii) {
+        QString filter = QString(
+            tr("Images") +
+            " (*.png *.bmp *.xbm *.jpg *.png *.xpm *.gif *.pnm *.svg);;" +
+            tr("All", "Filedialog") + " (*.*)");
+        if (fn.isEmpty())
+            fn = QFileDialog::getSaveFileName(
+                NULL, vymName + " - " + tr("Save image"), lastImageDir.path(),
+                filter, NULL, QFileDialog::DontConfirmOverwrite);
+
+        if (!fn.isEmpty()) {
+            lastImageDir.setPath(fn.left(fn.lastIndexOf("/")));
+            if (QFile(fn).exists()) {
+                QMessageBox mb(vymName,
+                               tr("The file %1 exists already.\n"
+                                  "Do you want to overwrite it?")
+                                   .arg(fn),
+                               QMessageBox::Warning,
+                               QMessageBox::Yes | QMessageBox::Default,
+                               QMessageBox::Cancel | QMessageBox::Escape,
+                               QMessageBox::NoButton);
+
+                mb.setButtonText(QMessageBox::Yes, tr("Overwrite"));
+                mb.setButtonText(QMessageBox::No, tr("Cancel"));
+                switch (mb.exec()) {
+                case QMessageBox::Yes:
+                    // save
+                    break;
+                case QMessageBox::Cancel:
+                    // do nothing
+                    return;
+                    break;
+                }
+            }
+            if (!ii->saveImage(fn))
+                QMessageBox::critical(0, tr("Critical Error"),
+                                      tr("Couldn't save %1").arg(fn));
+        }
+    }
+}
+
+void VymModel::importDirInt(BranchItem *dst, QDir d)
+{
+    bool oldSaveState = saveStateBlocked;
+    saveStateBlocked = true;
+    BranchItem *bi = dst;
+    if (bi) {
+        int beginDepth = bi->depth();
+
+        d.setFilter(QDir::AllEntries | QDir::Hidden);
+        QFileInfoList list = d.entryInfoList();
+        QFileInfo fi;
+
+        // Traverse directories
+        for (int i = 0; i < list.size(); ++i) {
+            fi = list.at(i);
+            if (fi.isDir() && fi.fileName() != "." && fi.fileName() != "..") {
+                bi = addNewBranchInt(dst, -2);
+                bi->setHeadingPlainText(fi.fileName());
+                bi->setHeadingColor(QColor("blue"));
+                if (debug)
+                    qDebug() << "Added subdir: " << fi.fileName();
+                if (!d.cd(fi.fileName()))
+                    QMessageBox::critical(
+                        0, tr("Critical Import Error"),
+                        tr("Cannot find the directory %1").arg(fi.fileName()));
+                else {
+                    // Recursively add subdirs
+                    importDirInt(bi, d);
+                    d.cdUp();
+                }
+                emitDataChanged(bi);
+            }
+        }
+
+        for (int i = 0; i < list.size(); ++i) {
+            fi = list.at(i);
+            if (fi.isFile()) {
+                bi = addNewBranchInt(dst, -2);
+                bi->setHeadingPlainText(fi.fileName());
+                bi->setHeadingColor(QColor("black"));
+                if (fi.fileName().right(4) == ".vym")
+                    bi->setVymLink(fi.filePath());
+                emitDataChanged(bi);
+            }
+        }
+
+        // Scroll at least some stuff
+        if (dst->branchCount() > 1 && dst->depth() - beginDepth > 2)
+            dst->toggleScroll();
+    }
+    saveStateBlocked = oldSaveState;
+}
+
+void VymModel::importDir(const QString &s)
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        saveStateChangingPart(
+            selbi, selbi, QString("importDir (\"%1\")").arg(s),
+            QString("Import directory structure from %1").arg(s));
+
+        QDir d(s);
+        importDirInt(selbi, d);
+    }
+}
+
+void VymModel::importDir()
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        QStringList filters;
+        filters << "VYM map (*.vym)";
+        QFileDialog fd;
+        fd.setWindowTitle(vymName + " - " +
+                          tr("Choose directory structure to import"));
+        fd.setFileMode(QFileDialog::DirectoryOnly);
+        fd.setNameFilters(filters);
+        fd.setWindowTitle(vymName + " - " +
+                          tr("Choose directory structure to import"));
+        fd.setAcceptMode(QFileDialog::AcceptOpen);
+
+        QString fn;
+        if (fd.exec() == QDialog::Accepted && !fd.selectedFiles().isEmpty()) {
+            importDir(fd.selectedFiles().first());
+            reposition();
+        }
+    }
+}
+
+bool VymModel::removeVymLock()
+{
+    if (vymLock.removeLockForced()) {
+        mainWindow->statusMessage(tr("Removed lockfile for %1").arg(mapName));
+        setReadOnly(false);
+        return true;
+    } else
+        return false;
+}
+
+bool VymModel::tryVymLock()
+{
+    // Defaults for author and host in vymLock
+    QString defAuthor =
+        settings
+            .value("/user/name",
+                   tr("unknown user", "Default for lockfiles of maps"))
+            .toString();
+    QString defHost = QHostInfo::localHostName();
+    vymLock.setMapPath(filePath);
+    vymLock.setAuthor(settings.value("/user/name", defAuthor).toString());
+    if (getenv("HOST") != 0)
+        vymLock.setHost(getenv("HOST"));
+    else
+        vymLock.setHost(defHost);
+
+    // Now try to lock
+    if (!vymLock.tryLock()) {
+        if (debug)
+            qDebug() << "VymModel::tryLock failed!";
+        setReadOnly(true);
+        if (vymLock.getState() == VymLock::LockedByOther) {
+            if (restoreMode) {
+                // While restoring maps, existing lockfiles will be ignored for
+                // loading, but listed in a warning dialog
+                ignoredLockedFiles << filePath;
+                return removeVymLock();
+            }
+            else {
+                LockedFileDialog dia;
+                QString a = vymLock.getAuthor();
+                QString h = vymLock.getHost();
+                QString s =
+                    QString(
+                        tr("Map seems to be already opened in another vym "
+                           "instance!\n\n "
+                           "Map is locked by \"%1\" on \"%2\"\n\n"
+                           "Please only delete the lockfile, if you are sure "
+                           "nobody else is currently working on this map."))
+                        .arg(a)
+                        .arg(h);
+                dia.setText(s);
+                dia.setWindowTitle(
+                    tr("Warning: Map already opended", "VymModel"));
+                if (dia.execDialog() == LockedFileDialog::DeleteLockfile) {
+                    if (!removeVymLock()) {
+                        // Could not remove existing lockfile, give up
+                        QMessageBox::warning(
+                            0, tr("Warning"),
+                            tr("Couldn't remove lockfile for %1").arg(mapName));
+                        return false;
+                    }
+                    if (!tryVymLock()) {
+                        // Was able to remove existing lockfile, but not able to 
+                        // create new one.
+                        qWarning() << "VymModel::tryVymLock could not create new lockfile after removing old";
+                        return false;
+                    }
+                }
+            }
+        }
+        else if (vymLock.getState() == VymLock::NotWritable) {
+            WarningDialog dia;
+            QString s = QString(tr("Cannot create lockfile of map! "
+                                   "It will be opened in readonly mode.\n\n"));
+            dia.setText(s);
+            dia.setWindowTitle(tr("Warning", "VymModel"));
+            dia.showCancelButton(false);
+            // dia.setShowAgainName("/mainwindow/mapIsLocked");
+            dia.exec();
+        }
+        return false;
+    }
+    return true;
+}
+
+bool VymModel::renameMap(const QString &newPath)
+{
+    QString oldPath = filePath;
+    if (vymLock.getState() == VymLock::LockedByMyself || vymLock.getState() == VymLock::Undefined) {
+        // vymModel owns the lockfile, try to create new lock
+        VymLock newLock;
+        newLock = vymLock;
+        newLock.setMapPath(newPath);    // Resets state for newLock to "Undefined"
+        if (!newLock.tryLock()) {
+            qWarning() << QString("VymModel::renameMap  could not create lockfile for %1").arg(newPath);
+            return false;
+        }
+
+        // Change lockfiles now
+        if (!vymLock.releaseLock())
+            qWarning() << "VymModel::renameMap failed to release lock for " << oldPath;
+        vymLock = newLock;
+        setFilePath(newPath);
+        return true;
+    }
+    qWarning() << "VymModel::renameMap failed to get lockfile. state=" << vymLock.getState();
+    return false;
+}
+
+void VymModel::setReadOnly(bool b)
+{
+    readonly = b;
+    mainWindow->updateTabName(this);
+}
+
+bool VymModel::isReadOnly() { return readonly; }
+
+void VymModel::autosave()
+{
+    // Check if autosave is disabled due to testmode
+    if (testmode)
+    {
+        qWarning()
+            << QString("VymModel::autosave disabled in testmode!  Current map: %1")
+                   .arg(filePath);
+        return;
+    }
+
+    // Check if autosave is disabled globally
+    if (!mainWindow->useAutosave()) {
+        qWarning()
+            << QString("VymModel::autosave disabled globally!  Current map: %1")
+                   .arg(filePath);
+        return;
+    }
+
+    QDateTime now = QDateTime().currentDateTime();
+
+    // Disable autosave, while we have gone back in history
+    int redosAvail = undoSet.numValue(QString("/history/redosAvail"));
+    if (redosAvail > 0)
+        return;
+
+    // Also disable autosave for new map without filename
+    if (filePath.isEmpty()) {
+        if (debug)
+            qWarning()
+                << "VymModel::autosave rejected due to missing filePath\n";
+        return;
+    }
+
+    if (mapUnsaved && mapChanged && mainWindow->useAutosave() && !testmode) {
+        if (QFileInfo(filePath).lastModified() <= fileChangedTime)
+            mainWindow->fileSave(this);
+        else if (debug)
+            qDebug() << "  ME::autosave  rejected, file on disk is newer than "
+                        "last save.\n";
+    }
+}
+
+void VymModel::fileChanged()
+{
+    // Check if file on disk has changed meanwhile
+    if (!filePath.isEmpty()) {
+        if (readonly) {
+            // unset readonly if lockfile is gone
+            if (vymLock.tryLock())
+                setReadOnly(false);
+        }
+        else {
+            // We could check, if somebody else removed/replaced lockfile
+            // (A unique vym ID would be needed)
+
+            QDateTime tmod = QFileInfo(filePath).lastModified();
+            if (tmod > fileChangedTime) {
+                // FIXME-4 VM switch to current mapeditor and finish
+                // lineedits...
+                QMessageBox mb(
+                    vymName,
+                    tr("The file of the map  on disk has changed:\n\n"
+                       "   %1\n\nDo you want to reload that map with the new "
+                       "file?")
+                        .arg(filePath),
+                    QMessageBox::Question, QMessageBox::Yes,
+                    QMessageBox::Cancel | QMessageBox::Default,
+                    QMessageBox::NoButton);
+
+                mb.setButtonText(QMessageBox::Yes, tr("Reload"));
+                mb.setButtonText(QMessageBox::No, tr("Ignore"));
+                switch (mb.exec()) {
+                case QMessageBox::Yes:
+                    // Reload map
+                    mainWindow->initProgressCounter(1);
+                    loadMap(filePath);
+                    mainWindow->removeProgressCounter();
+                    break;
+                case QMessageBox::Cancel:
+                    fileChangedTime =
+                        tmod; // allow autosave to overwrite newer file!
+                }
+            }
+        }
+    }
+}
+
+void VymModel::blockReposition()
+{
+    repositionBlocked = true;
+}
+
+void VymModel::unblockReposition()
+{
+    repositionBlocked = false;
+    reposition();
+}
+
+bool VymModel::isDefault() { return mapDefault; }
+
+void VymModel::makeDefault()
+{
+    mapChanged = false;
+    mapDefault = true;
+}
+
+bool VymModel::hasChanged() { return mapChanged; }
+
+void VymModel::setChanged()
+{
+    if (!mapChanged)
+        autosaveTimer->start(
+            settings.value("/system/autosave/ms/", 30000).toInt());
+    mapChanged = true;
+    mapDefault = false;
+    mapUnsaved = true;
+    updateActions();
+}
+
+QString VymModel::getObjectName(LinkableMapObj *lmo)
+{
+    if (!lmo || !lmo->getTreeItem())
+        return QString();
+    return getObjectName(lmo->getTreeItem());
+}
+
+QString VymModel::getObjectName(TreeItem *ti)
+{
+    QString s;
+    if (!ti)
+        return QString("Error: NULL has no name!");
+    s = ti->getHeadingPlain();
+    if (s == "")
+        s = "unnamed";
+
+    return QString("%1 (%2)").arg(ti->getTypeName()).arg(s);
+}
+
+void VymModel::redo()
+{
+    // Can we undo at all?
+    if (redosAvail < 1)
+        return;
+
+    bool saveStateBlockedOrg = saveStateBlocked;
+    saveStateBlocked = true;
+
+    redosAvail--;
+
+    if (undosAvail < stepsTotal)
+        undosAvail++;
+    curStep++;
+    if (curStep > stepsTotal)
+        curStep = 1;
+    QString undoCommand =
+        undoSet.value(QString("/history/step-%1/undoCommand").arg(curStep));
+    QString undoSelection =
+        undoSet.value(QString("/history/step-%1/undoSelection").arg(curStep));
+    QString redoCommand =
+        undoSet.value(QString("/history/step-%1/redoCommand").arg(curStep));
+    QString redoSelection =
+        undoSet.value(QString("/history/step-%1/redoSelection").arg(curStep));
+    QString comment =
+        undoSet.value(QString("/history/step-%1/comment").arg(curStep));
+    QString version = undoSet.value("/history/version");
+
+    /* TODO Maybe check for version, if we save the history
+    if (!checkVersion(version))
+    QMessageBox::warning(0,tr("Warning"),
+        tr("Version %1 of saved undo/redo data\ndoes not match current vym
+    version %2.").arg(version).arg(vymVersion));
+    */
+
+    // Find out current undo directory
+    QString bakMapDir(QString(tmpMapDirPath + "/undo-%1").arg(curStep));
+
+    if (debug) {
+        qDebug() << "VymModel::redo() begin\n";
+        qDebug() << "    undosAvail=" << undosAvail;
+        qDebug() << "    redosAvail=" << redosAvail;
+        qDebug() << "       curStep=" << curStep;
+        qDebug() << "    ---------------------------";
+        qDebug() << "    comment=" << comment;
+        qDebug() << "    undoSel=" << undoSelection;
+        qDebug() << "    redoSel=" << redoSelection;
+        qDebug() << "    undoCom:";
+        cout << qPrintable(undoCommand);
+        qDebug() << "    redoCom=";
+        cout << qPrintable(redoCommand);
+        qDebug() << "    ---------------------------";
+    }
+
+    // select  object before redo
+    if (!redoSelection.isEmpty())
+        select(redoSelection);
+
+    QString errMsg;
+    QString redoScript =
+        QString("model = vym.currentMap(); model.%1").arg(redoCommand);
+    errMsg = QVariant(execute(redoScript)).toString();
+    saveStateBlocked = saveStateBlockedOrg;
+
+    undoSet.setValue("/history/undosAvail", QString::number(undosAvail));
+    undoSet.setValue("/history/redosAvail", QString::number(redosAvail));
+    undoSet.setValue("/history/curStep", QString::number(curStep));
+    undoSet.writeSettings(histPath);
+
+    mainWindow->updateHistory(undoSet);
+    updateActions();
+
+    /* TODO remove testing
+    qDebug() << "ME::redo() end\n";
+    qDebug() << "    undosAvail="<<undosAvail;
+    qDebug() << "    redosAvail="<<redosAvail;
+    qDebug() << "       curStep="<<curStep;
+    qDebug() << "    ---------------------------";
+    */
+}
+
+bool VymModel::isRedoAvailable()
+{
+    if (undoSet.numValue("/history/redosAvail", 0) > 0)
+        return true;
+    else
+        return false;
+}
+
+QString VymModel::lastRedoSelection()
+{
+    if (isUndoAvailable())
+        return undoSet.value(
+            QString("/history/step-%1/redoSelection").arg(curStep));
+    else
+        return QString();
+}
+
+QString VymModel::lastRedoCommand()
+{
+    if (isUndoAvailable())
+        return undoSet.value(
+            QString("/history/step-%1/redoCommand").arg(curStep));
+    else
+        return QString();
+}
+
+QVariant VymModel::repeatLastCommand()
+{
+    QString command = "m = vym.currentMap();";
+    if (isUndoAvailable())
+        command += "m." +
+                   undoSet.value(
+                       QString("/history/step-%1/redoCommand").arg(curStep)) +
+                   ";";
+    else
+        return false;
+    return execute(command);
+}
+
+void VymModel::undo()
+{
+    // Can we undo at all?
+    if (undosAvail < 1)
+        return;
+
+    mainWindow->statusMessage(tr("Autosave disabled during undo."));
+
+    bool saveStateBlockedOrg = saveStateBlocked;
+    saveStateBlocked = true;
+
+    QString undoCommand =
+        undoSet.value(QString("/history/step-%1/undoCommand").arg(curStep));
+    QString undoSelection =
+        undoSet.value(QString("/history/step-%1/undoSelection").arg(curStep));
+    QString redoCommand =
+        undoSet.value(QString("/history/step-%1/redoCommand").arg(curStep));
+    QString redoSelection =
+        undoSet.value(QString("/history/step-%1/redoSelection").arg(curStep));
+    QString comment =
+        undoSet.value(QString("/history/step-%1/comment").arg(curStep));
+    QString version = undoSet.value("/history/version");
+
+    /* TODO Maybe check for version, if we save the history
+    if (!checkVersion(version))
+    QMessageBox::warning(0,tr("Warning"),
+        tr("Version %1 of saved undo/redo data\ndoes not match current vym
+    version %2.").arg(version).arg(vymVersion));
+    */
+
+    // Find out current undo directory
+    QString bakMapDir(QString(tmpMapDirPath + "/undo-%1").arg(curStep));
+
+    // select  object before undo
+    if (!undoSelection.isEmpty() && !select(undoSelection)) {
+        qWarning("VymModel::undo()  Could not select object for undo");
+        return;
+    }
+
+    if (debug) {
+        qDebug() << "VymModel::undo() begin\n";
+        qDebug() << "    undosAvail=" << undosAvail;
+        qDebug() << "    redosAvail=" << redosAvail;
+        qDebug() << "       curStep=" << curStep;
+        cout << "    ---------------------------" << endl;
+        qDebug() << "    comment=" << comment;
+        qDebug() << "    undoSel=" << undoSelection;
+        qDebug() << "    redoSel=" << redoSelection;
+        cout << "    undoCom:" << endl;
+        cout << qPrintable(undoCommand) << endl;
+        cout << "    redoCom:" << endl;
+        cout << qPrintable(redoCommand) << endl;
+        cout << "    ---------------------------" << endl;
+    }
+
+    // select  object before undo   // FIXME-2 double select, see above
+    if (!undoSelection.isEmpty())
+        select(undoSelection);
+
+    // bool noErr;
+    QString errMsg;
+    QString undoScript =
+        QString("model = vym.currentMap(); model.%1").arg(undoCommand);
+    errMsg = QVariant(execute(undoScript)).toString();
+
+    undosAvail--;
+    curStep--;
+    if (curStep < 1)
+        curStep = stepsTotal;
+
+    redosAvail++;
+
+    saveStateBlocked = saveStateBlockedOrg;
+    /* testing only
+        qDebug() << "VymModel::undo() end\n";
+        qDebug() << "    undosAvail="<<undosAvail;
+        qDebug() << "    redosAvail="<<redosAvail;
+        qDebug() << "       curStep="<<curStep;
+        qDebug() << "    ---------------------------";
+    */
+
+    undoSet.setValue("/history/undosAvail", QString::number(undosAvail));
+    undoSet.setValue("/history/redosAvail", QString::number(redosAvail));
+    undoSet.setValue("/history/curStep", QString::number(curStep));
+    undoSet.writeSettings(histPath);
+
+    mainWindow->updateHistory(undoSet);
+    updateActions();
+}
+
+bool VymModel::isUndoAvailable()
+{
+    if (undoSet.numValue("/history/undosAvail", 0) > 0)
+        return true;
+    return false;
+}
+
+void VymModel::gotoHistoryStep(int i)
+{
+    // Restore variables
+    int undosAvail = undoSet.numValue(QString("/history/undosAvail"));
+    int redosAvail = undoSet.numValue(QString("/history/redosAvail"));
+
+    if (i < 0)
+        i = undosAvail + redosAvail;
+
+    // Clicking above current step makes us undo things
+    if (i < undosAvail) {
+        for (int j = 0; j < undosAvail - i; j++)
+            undo();
+        return;
+    }
+    // Clicking below current step makes us redo things
+    if (i > undosAvail)
+        for (int j = undosAvail; j < i; j++) {
+            if (debug)
+                qDebug() << "VymModel::gotoHistoryStep redo " << j << "/"
+                         << undosAvail << " i=" << i;
+            redo();
+        }
+
+    // And ignore clicking the current row ;-)
+}
+
+QString VymModel::getHistoryPath()
+{
+    QString histName(QString("history-%1").arg(curStep));
+    return (tmpMapDirPath + "/" + histName);
+}
+
+void VymModel::resetHistory()
+{
+    curStep = 0;
+    redosAvail = 0;
+    undosAvail = 0;
+
+    stepsTotal = settings.value("/history/stepsTotal", 100).toInt();
+    undoSet.setValue("/history/stepsTotal", QString::number(stepsTotal));
+    mainWindow->updateHistory(undoSet);
+}
+
+void VymModel::saveState(const SaveMode &savemode, const QString &undoSelection,
+                         const QString &undoCom, const QString &redoSelection,
+                         const QString &redoCom, const QString &comment,
+                         TreeItem *saveSel, QString dataXML)
+{
+    sendData(redoCom); // FIXME-4 testing
+
+    // Main saveState
+
+    if (saveStateBlocked)
+        return;
+
+    if (debug)
+        qDebug() << "VM::saveState() for map " << mapName;
+
+    QString undoCommand = undoCom;
+    QString redoCommand = redoCom;
+
+
+    // Increase undo steps, but check for repeated actions
+    // like editing a vymNote - then do not increase but replace last command
+    /*
+    QRegExp re ("parseVymText.*\\(.*vymnote");
+    if (curStep > 0 && redoSelection == lastRedoSelection() &&
+        lastRedoCommand().contains(re)) {
+        undoCommand = undoSet.value(
+            QString("/history/step-%1/undoCommand").arg(curStep), undoCommand);
+    }
+    else {
+    */
+        if (undosAvail < stepsTotal)
+            undosAvail++;
+
+        curStep++;
+        if (curStep > stepsTotal)
+            curStep = 1;
+    //}
+
+    QString histDir = getHistoryPath();
+    QString bakMapPath = histDir + "/map.xml";
+
+    // Create histDir if not available
+    QDir d(histDir);
+    if (!d.exists())
+        makeSubDirs(histDir);
+
+    // Save depending on how much needs to be saved
+    QList<Link *> tmpLinks;
+    if (saveSel)
+        dataXML = saveToDir(histDir, mapName + "-", FlagRowMaster::NoFlags, QPointF(),
+                            saveSel);
+
+    if (savemode == PartOfMap) {
+        undoCommand.replace("PATH", bakMapPath);
+        redoCommand.replace("PATH", bakMapPath);
+    }
+
+    if (!dataXML.isEmpty())
+        // Write XML Data to disk
+        saveStringToDisk(bakMapPath, dataXML);
+
+    // We would have to save all actions in a tree, to keep track of
+    // possible redos after a action. Possible, but we are too lazy: forget
+    // about redos.
+    redosAvail = 0;
+
+    // Write the current state to disk
+    undoSet.setValue("/history/undosAvail", QString::number(undosAvail));
+    undoSet.setValue("/history/redosAvail", QString::number(redosAvail));
+    undoSet.setValue("/history/curStep", QString::number(curStep));
+    undoSet.setValue(QString("/history/step-%1/undoCommand").arg(curStep),
+                     undoCommand);
+    undoSet.setValue(QString("/history/step-%1/undoSelection").arg(curStep),
+                     undoSelection);
+    undoSet.setValue(QString("/history/step-%1/redoCommand").arg(curStep),
+                     redoCommand);
+    undoSet.setValue(QString("/history/step-%1/redoSelection").arg(curStep),
+                     redoSelection);
+    undoSet.setValue(QString("/history/step-%1/comment").arg(curStep), comment);
+    undoSet.setValue(QString("/history/version"), vymVersion);
+    undoSet.writeSettings(histPath);
+
+    if (debug) {
+        // qDebug() << "          into="<< histPath;
+        qDebug() << "    stepsTotal=" << stepsTotal
+                 << ", undosAvail=" << undosAvail
+                 << ", redosAvail=" << redosAvail << ", curStep=" << curStep;
+        cout << "    ---------------------------" << endl;
+        qDebug() << "    comment=" << comment;
+        qDebug() << "    undoSel=" << undoSelection;
+        qDebug() << "    redoSel=" << redoSelection;
+        if (saveSel)
+            qDebug() << "    saveSel=" << qPrintable(getSelectString(saveSel));
+        cout << "    undoCom:" << endl;
+        cout << qPrintable(undoCommand) << endl;
+        cout << "    redoCom:" << endl;
+        cout << qPrintable(redoCommand) << endl;
+        cout << "    ---------------------------" << endl;
+    }
+
+    mainWindow->updateHistory(undoSet);
+
+    setChanged();
+}
+
+void VymModel::saveStateChangingPart(TreeItem *undoSel, TreeItem *redoSel,
+                                     const QString &rc, const QString &comment)
+{
+    // save the selected part of the map, Undo will replace part of map
+    QString undoSelection = "";
+    if (undoSel)
+        undoSelection = getSelectString(undoSel);
+    else
+        qWarning("VymModel::saveStateChangingPart  no undoSel given!");
+    QString redoSelection = "";
+    if (redoSel)
+        redoSelection = getSelectString(undoSel);
+    else
+        qWarning("VymModel::saveStateChangingPart  no redoSel given!");
+
+    saveState(PartOfMap, undoSelection, "addMapReplace (\"PATH\")",
+              redoSelection, rc, comment, undoSel);
+}
+
+void VymModel::saveStateRemovingPart(TreeItem *redoSel, const QString &comment)
+{
+    if (!redoSel) {
+        qWarning("VymModel::saveStateRemovingPart  no redoSel given!");
+        return;
+    }
+    QString undoSelection;
+    QString redoSelection = getSelectString(redoSel);
+    if (redoSel->isBranchLikeType()) {
+        // save the selected branch of the map, Undo will insert part of map
+        if (redoSel->depth() > 0)
+            undoSelection = getSelectString(redoSel->parent());
+        saveState(PartOfMap, undoSelection,
+                  QString("addMapInsert (\"PATH\",%1,%2)")
+                      .arg(redoSel->num())
+                      .arg(SlideContent),
+                  redoSelection, "remove ()", comment, redoSel);
+    }
+}
+
+void VymModel::saveState(TreeItem *undoSel, const QString &uc,
+                         TreeItem *redoSel, const QString &rc,
+                         const QString &comment)
+{
+    // "Normal" savestate: save commands, selections and comment
+    // so just save commands for undo and redo
+    // and use current selection, if empty parameter passed
+
+    QString redoSelection = "";
+    if (redoSel)
+        redoSelection = getSelectString(redoSel);
+    QString undoSelection = "";
+    if (undoSel)
+        undoSelection = getSelectString(undoSel);
+
+    saveState(UndoCommand, undoSelection, uc, redoSelection, rc, comment, NULL);
+}
+
+void VymModel::saveState(const QString &undoSel, const QString &uc,
+                         const QString &redoSel, const QString &rc,
+                         const QString &comment)
+{
+    // "Normal" savestate: save commands, selections and comment
+    // so just save commands for undo and redo
+    // and use current selection
+    saveState(UndoCommand, undoSel, uc, redoSel, rc, comment, NULL);
+}
+
+void VymModel::saveState(const QString &uc, const QString &rc,
+                         const QString &comment)
+{
+    // "Normal" savestate applied to model (no selection needed):
+    // save commands  and comment
+    saveState(UndoCommand, NULL, uc, NULL, rc, comment, NULL);
+}
+
+void VymModel::saveStateMinimal(TreeItem *undoSel, const QString &uc,
+                                TreeItem *redoSel, const QString &rc,
+                                const QString &comment)
+{ //  Save a change in string and merge
+    //  minor sequential  changes  */
+    QString redoSelection = "";
+    if (redoSel)
+        redoSelection = getSelectString(redoSel);
+    QString undoSelection = "";
+    if (undoSel)
+        undoSelection = getSelectString(undoSel);
+
+    saveState(UndoCommand, undoSelection, uc, redoSelection, rc, comment, NULL);
+}
+
+void VymModel::saveStateBeforeLoad(LoadMode lmode, const QString &fname)
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        if (lmode == ImportAdd)
+            saveStateChangingPart(selbi, selbi,
+                                  QString("addMapInsert (\"%1\")").arg(fname),
+                                  QString("Add map %1 to %2")
+                                      .arg(fname)
+                                      .arg(getObjectName(selbi)));
+        if (lmode == ImportReplace) {
+            BranchItem *pi = (BranchItem *)(selbi->parent());
+            saveStateChangingPart(pi, pi,
+                                  QString("addMapReplace(%1)").arg(fname),
+                                  QString("Add map %1 to %2")
+                                      .arg(fname)
+                                      .arg(getObjectName(selbi)));
+        }
+    }
+}
+
+QGraphicsScene *VymModel::getScene() { return mapEditor->getScene(); }
+
+TreeItem *VymModel::findBySelectString(QString s)
+{
+    if (s.isEmpty())
+        return NULL;
+
+    // Old maps don't have multiple mapcenters and don't save full path
+    if (s.left(2) != "mc")
+        s = "mc:0," + s;
+
+    QStringList parts = s.split(",");
+    QString typ;
+    int n;
+    TreeItem *ti = rootItem;
+
+    while (!parts.isEmpty()) {
+        typ = parts.first().left(2);
+        n = parts.first().right(parts.first().length() - 3).toInt();
+        parts.removeFirst();
+        if (typ == "mc" || typ == "bo")
+            ti = ti->getBranchNum(n);
+        else if (typ == "fi")
+            ti = ti->getImageNum(n);
+        else if (typ == "ai")
+            ti = ti->getAttributeNum(n);
+        else if (typ == "xl")
+            ti = ti->getXLinkItemNum(n);
+        if (!ti)
+            return NULL;
+    }
+    return ti;
+}
+
+TreeItem *VymModel::findID(const uint &id)
+{
+    BranchItem *cur = NULL;
+    BranchItem *prev = NULL;
+    nextBranch(cur, prev);
+    while (cur) {
+        if (id == cur->getID())
+            return cur;
+        int j = 0;
+        while (j < cur->xlinkCount()) {
+            XLinkItem *xli = cur->getXLinkItemNum(j);
+            if (id == xli->getID())
+                return xli;
+            j++;
+        }
+        j = 0;
+        while (j < cur->imageCount()) {
+            ImageItem *ii = cur->getImageNum(j);
+            if (id == ii->getID())
+                return ii;
+            j++;
+        }
+        nextBranch(cur, prev);
+    }
+    return NULL;
+}
+
+TreeItem *VymModel::findUuid(const QUuid &id)
+{
+    BranchItem *cur = NULL;
+    BranchItem *prev = NULL;
+    nextBranch(cur, prev);
+    while (cur) {
+        if (id == cur->getUuid())
+            return cur;
+        int j = 0;
+        while (j < cur->xlinkCount()) {
+            XLinkItem *xli = cur->getXLinkItemNum(j);
+            if (id == xli->getUuid())
+                return xli;
+            j++;
+        }
+        j = 0;
+        while (j < cur->imageCount()) {
+            ImageItem *ii = cur->getImageNum(j);
+            if (id == ii->getUuid())
+                return ii;
+            j++;
+        }
+        nextBranch(cur, prev);
+    }
+    return NULL;
+}
+
+//////////////////////////////////////////////
+// Interface
+//////////////////////////////////////////////
+void VymModel::setVersion(const QString &s) { version = s; }
+
+QString VymModel::getVersion() { return version; }
+
+void VymModel::setTitle(const QString &s)
+{
+    saveState(QString("setMapTitle (\"%1\")").arg(title),
+              QString("setMapTitle (\"%1\")").arg(s),
+              QString("Set title of map to \"%1\"").arg(s));
+    title = s;
+}
+
+QString VymModel::getTitle() { return title; }
+
+void VymModel::setAuthor(const QString &s)
+{
+    saveState(QString("setMapAuthor (\"%1\")").arg(author),
+              QString("setMapAuthor (\"%1\")").arg(s),
+              QString("Set author of map to \"%1\"").arg(s));
+    author = s;
+}
+
+QString VymModel::getAuthor() { return author; }
+
+void VymModel::setComment(const QString &s)
+{
+    saveState(QString("setMapComment (\"%1\")").arg(comment),
+              QString("setMapComment (\"%1\")").arg(s),
+              QString("Set comment of map"));
+    comment = s;
+}
+
+QString VymModel::getComment() { return comment; }
+
+QString VymModel::getDate()
+{
+    return QDate::currentDate().toString("yyyy-MM-dd");
+}
+
+int VymModel::branchCount()
+{
+    int c = 0;
+    BranchItem *cur = NULL;
+    BranchItem *prev = NULL;
+    nextBranch(cur, prev);
+    while (cur) {
+        c++;
+        nextBranch(cur, prev);
+    }
+    return c;
+}
+
+int VymModel::centerCount() { return rootItem->branchCount(); }
+
+void VymModel::setSortFilter(const QString &s)
+{
+    sortFilter = s;
+    emit(sortFilterChanged(sortFilter));
+}
+
+QString VymModel::getSortFilter() { return sortFilter; }
+
+void VymModel::setHeading(const VymText &vt, BranchItem *bi)
+{
+    Heading h_old;
+    Heading h_new;
+    h_new = vt;
+    QString s = vt.getTextASCII();
+    if (!bi)
+        bi = getSelectedBranch();
+    if (bi) {
+        h_old = bi->getHeading();
+        if (h_old == h_new)
+            return;
+        saveState(bi, "parseVymText (\"" + quoteQuotes(h_old.saveToDir()) + "\")", bi,
+                  "parseVymText (\"" + quoteQuotes(h_new.saveToDir()) + "\")",
+                  QString("Set heading of %1 to \"%2\"")
+                      .arg(getObjectName(bi))
+                      .arg(s));
+        bi->setHeading(vt);
+        emitDataChanged(bi);
+        emitUpdateQueries();
+        mainWindow->updateHeadingEditor(bi);    // Update HeadingEditor with new heading
+        reposition();
+    }
+}
+
+void VymModel::setHeadingPlainText(const QString &s, BranchItem *bi)
+{
+    if (!bi)
+        bi = getSelectedBranch();
+    if (bi) {
+        VymText vt = bi->getHeading();
+        vt.setPlainText(s);
+        if (bi->getHeading() == vt)
+            return;
+        setHeading(vt, bi);
+
+        // Set URL
+        if ((s.startsWith("http://") || s.startsWith("https://")) &&
+            bi->getURL().isEmpty())
+            setURL(s);
+    }
+}
+
+Heading VymModel::getHeading()
+{
+    TreeItem *selti = getSelectedItem();
+    if (selti)
+        return selti->getHeading();
+    qWarning() << "VymModel::getHeading Nothing selected.";
+    return Heading();
+}
+
+void VymModel::updateNoteText(const VymText &vt)
+{
+    bool editorStateChanged = false;
+
+    TreeItem *selti = getSelectedItem();
+    if (selti) {
+        VymNote note_old = selti->getNote();
+        VymNote note_new(vt);
+        if (note_new.getText() != note_old.getText()) {
+            if ((note_new.isEmpty() && !note_old.isEmpty()) ||
+                (!note_new.isEmpty() && note_old.isEmpty()))
+                editorStateChanged = true;
+
+            VymNote vn;
+            vn.copy(vt);
+
+            saveState(selti, "parseVymText (\"" + quoteQuotes(note_old.saveToDir()) + "\")",
+                      selti, "parseVymText (\"" + quoteQuotes(note_new.saveToDir()) + "\")",
+                      QString("Set note of %1 to \"%2\"")
+                          .arg(getObjectName(selti))
+                          .arg(note_new.getTextASCII().left(20)));
+
+            selti->setNote(vn);
+        }
+
+        // Update also flags after changes in NoteEditor
+        emitDataChanged(selti);
+
+        // Only update flag, if state has changed
+        if (editorStateChanged)
+            reposition();
+    }
+}
+
+void VymModel::setNote(const VymNote &vn)
+{
+    TreeItem *selti = getSelectedItem();
+    if (selti) {
+        VymNote n_old;
+        VymNote n_new;
+        n_old = selti->getNote();
+        n_new = vn;
+        saveState(selti, "parseVymText (\"" + quoteQuotes(n_old.saveToDir()) + "\")", selti,
+                  "parseVymText (\"" + quoteQuotes(n_new.saveToDir()) + "\")",
+                  QString("Set note of %1 to \"%2\"")
+                      .arg(getObjectName(selti))
+                      .arg(n_new.getTextASCII().left(40)));
+        selti->setNote(n_new);
+        emitNoteChanged(selti);
+        emitDataChanged(selti);
+    }
+}
+
+VymNote VymModel::getNote()
+{
+    TreeItem *selti = getSelectedItem();
+    if (selti) {
+        VymNote n = selti->getNote();
+        return n;
+    }
+    qWarning() << "VymModel::getNote Nothing selected.";
+    return VymNote();
+}
+
+bool VymModel::hasRichTextNote()
+{
+    TreeItem *selti = getSelectedItem();
+    if (selti) {
+        return selti->getNote().isRichText();
+    }
+    return false;
+}
+
+void VymModel::loadNote(const QString &fn)
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        QString n;
+        if (!loadStringFromDisk(fn, n))
+            qWarning() << "VymModel::loadNote Couldn't load " << fn;
+        else {
+            VymNote vn;
+            vn.setAutoText(n);
+            setNote(vn);
+            emitDataChanged(selbi);
+            emitUpdateQueries();
+            reposition();
+        }
+    }
+    else
+        qWarning("VymModel::loadNote no branch selected");
+}
+
+void VymModel::saveNote(const QString &fn)
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        VymNote n = selbi->getNote();
+        if (n.isEmpty())
+            qWarning() << "VymModel::saveNote  note is empty, won't save to "
+                       << fn;
+        else {
+            if (!saveStringToDisk(fn, n.saveToDir()))
+                qWarning() << "VymModel::saveNote Couldn't save " << fn;
+        }
+    }
+    else
+        qWarning("VymModel::saveNote no branch selected");
+}
+
+void VymModel::findDuplicateURLs() // FIXME-3 needs GUI
+{
+    // Generate map containing _all_ URLs and branches
+    QString u;
+    QMultiMap<QString, BranchItem *> map;
+    BranchItem *cur = NULL;
+    BranchItem *prev = NULL;
+    nextBranch(cur, prev);
+    while (cur) {
+        u = cur->getURL();
+        if (!u.isEmpty())
+            map.insert(u, cur);
+        nextBranch(cur, prev);
+    }
+
+    // Extract duplicate URLs
+    QMultiMap<QString, BranchItem *>::const_iterator i = map.constBegin();
+    QMultiMap<QString, BranchItem *>::const_iterator firstdup =
+        map.constEnd(); // invalid
+    while (i != map.constEnd()) {
+        if (i != map.constBegin() && i.key() == firstdup.key()) {
+            if (i - 1 == firstdup) {
+                qDebug() << firstdup.key();
+                qDebug() << " - " << firstdup.value() << " - "
+                         << firstdup.value()->getHeading().getText();
+            }
+            qDebug() << " - " << i.value() << " - "
+                     << i.value()->getHeading().getText();
+        }
+        else
+            firstdup = i;
+
+        ++i;
+    }
+}
+
+bool VymModel::findAll(FindResultModel *rmodel, QString s,
+                       Qt::CaseSensitivity cs, bool searchNotes)
+{
+    rmodel->clear();
+    rmodel->setSearchString(s);
+    rmodel->setSearchFlags(QTextDocument::FindFlags()); // FIXME-4 translate cs to
+                               // QTextDocument::FindFlag
+    bool hit = false;
+
+    BranchItem *cur = NULL;
+    BranchItem *prev = NULL;
+    nextBranch(cur, prev);
+
+    FindResultItem *lastParent = NULL;
+    while (cur) {
+        lastParent = NULL;
+        if (cur->getHeading().getTextASCII().contains(s, cs)) {
+            lastParent = rmodel->addItem(cur);
+            hit = true;
+        }
+
+        if (searchNotes) {
+            QString n = cur->getNoteASCII();
+            int i = 0;
+            int j = 0;
+            while (i >= 0) {
+                i = n.indexOf(s, i, cs);
+                if (i >= 0) {
+                    // If not there yet, add "parent" item
+                    if (!lastParent) {
+                        lastParent = rmodel->addItem(cur);
+                        hit = true;
+                        if (!lastParent)
+                            qWarning()
+                                << "VymModel::findAll still no lastParent?!";
+                        /*
+                        else
+                            lastParent->setSelectable (false);
+                        */
+                    }
+
+                    // save index of occurence
+                    QString e = n.mid(i - 15, 30);
+                    n.replace('\n', ' ');
+                    rmodel->addSubItem(
+                        lastParent,
+                        QString(tr("Note", "FindAll in VymModel") +
+                                ": \"...%1...\"")
+                            .arg(n.mid(i - 8, 80)),
+                        cur, j);
+                    j++;
+                    i++;
+                }
+            }
+        }
+        nextBranch(cur, prev);
+    }
+    return hit;
+}
+
+void VymModel::setURL(QString url, bool updateFromCloud, BranchItem *bi)
+{
+    if (!bi) bi = getSelectedBranch();
+    if (bi->getURL() == url)
+        return;
+
+    if (bi) {
+        QString oldurl = bi->getURL();
+        bi->setURL(url);
+        saveState(
+            bi, QString("setURL (\"%1\")").arg(oldurl), bi,
+            QString("setURL (\"%1\")").arg(url),
+            QString("set URL of %1 to %2").arg(getObjectName(bi)).arg(url));
+        emitDataChanged(bi);
+        reposition();
+
+        if (updateFromCloud)    // FIXME-2 use oembed.com also for Youtube and other cloud providers
+            // Check for Confluence
+            setHeadingConfluencePageName();
+    }
+}
+
+QString VymModel::getURL()
+{
+    TreeItem *selti = getSelectedItem();
+    if (selti)
+        return selti->getURL();
+    else
+        return QString();
+}
+
+QStringList VymModel::getURLs(bool ignoreScrolled)
+{
+    QStringList urls;
+    BranchItem *selbi = getSelectedBranch();
+    BranchItem *cur = NULL;
+    BranchItem *prev = NULL;
+    nextBranch(cur, prev, true, selbi);
+    while (cur) {
+        if (!cur->getURL().isEmpty() &&
+            !(ignoreScrolled && cur->hasScrolledParent()))
+            urls.append(cur->getURL());
+        nextBranch(cur, prev, true, selbi);
+    }
+    return urls;
+}
+
+void VymModel::setFrameType(const FrameObj::FrameType &t)
+{
+    BranchItem *bi = getSelectedBranch();
+    if (bi) {
+        BranchObj *bo = (BranchObj *)(bi->getLMO());
+        if (bo) {
+            QString s = bo->getFrameTypeName();
+            bo->setFrameType(t);
+            saveState(
+                bi, QString("setFrameType (\"%1\")").arg(s), bi,
+                QString("setFrameType (\"%1\")").arg(bo->getFrameTypeName()),
+                QString("set type of frame to %1").arg(s));
+            reposition();
+            bo->updateLinkGeometry();
+        }
+    }
+}
+
+void VymModel::setFrameType(const QString &s)
+{
+    BranchItem *bi = getSelectedBranch();
+    if (bi) {
+        BranchObj *bo = (BranchObj *)(bi->getLMO());
+        if (bo) {
+            saveState(
+                bi,
+                QString("setFrameType (\"%1\")").arg(bo->getFrameTypeName()),
+                bi, QString("setFrameType (\"%1\")").arg(s),
+                QString("set type of frame to %1").arg(s));
+            bo->setFrameType(s);
+            reposition();
+            bo->updateLinkGeometry();
+        }
+    }
+}
+
+void VymModel::toggleFrameIncludeChildren()
+{
+    BranchItem *bi = getSelectedBranch();
+    if (bi) {
+        bool b = bi->getFrameIncludeChildren();
+        setFrameIncludeChildren(!b);
+    }
+}
+
+void VymModel::setFrameIncludeChildren(bool b)
+{
+    BranchItem *bi = getSelectedBranch();
+    if (bi) {
+        QString u = b ? "false" : "true";
+        QString r = !b ? "false" : "true";
+
+        saveState(bi, QString("setFrameIncludeChildren(%1)").arg(u), bi,
+                  QString("setFrameIncludeChildren(%1)").arg(r),
+                  QString("Include children in %1").arg(getObjectName(bi)));
+        bi->setFrameIncludeChildren(b);
+        emitDataChanged(bi);
+        reposition();
+    }
+}
+
+void VymModel::setFramePenColor(
+    const QColor &c) // FIXME-4 not saved if there is no LMO
+
+{
+    BranchItem *bi = getSelectedBranch();
+    if (bi) {
+        BranchObj *bo = (BranchObj *)(bi->getLMO());
+        if (bo) {
+            saveState(bi,
+                      QString("setFramePenColor (\"%1\")")
+                          .arg(bo->getFramePenColor().name()),
+                      bi, QString("setFramePenColor (\"%1\")").arg(c.name()),
+                      QString("set pen color of frame to %1").arg(c.name()));
+            bo->setFramePenColor(c);
+        }
+    }
+}
+
+void VymModel::setFrameBrushColor(
+    const QColor &c) // FIXME-4 not saved if there is no LMO
+{
+    BranchItem *bi = getSelectedBranch();
+    if (bi) {
+        BranchObj *bo = (BranchObj *)(bi->getLMO());
+        if (bo) {
+            saveState(bi,
+                      QString("setFrameBrushColor (\"%1\")")
+                          .arg(bo->getFrameBrushColor().name()),
+                      bi, QString("setFrameBrushColor (\"%1\")").arg(c.name()),
+                      QString("set brush color of frame to %1").arg(c.name()));
+            bo->setFrameBrushColor(c);
+            bi->setBackgroundColor(c); // FIXME-4 redundant with above
+        }
+        emitDataChanged(bi);  // Notify HeadingEditor to eventually change BG color
+    }
+}
+
+void VymModel::setFramePadding(
+    const int &i)
+{
+    BranchItem *bi = getSelectedBranch();
+    if (bi) {
+        BranchObj *bo = (BranchObj *)(bi->getLMO());
+        if (bo) {
+            saveState(
+                bi,
+                QString("setFramePadding (\"%1\")").arg(bo->getFramePadding()),
+                bi, QString("setFramePadding (\"%1\")").arg(i),
+                QString("set brush color of frame to %1").arg(i));
+            bo->setFramePadding(i);
+            reposition();
+            bo->updateLinkGeometry();
+        }
+    }
+}
+
+void VymModel::setFrameBorderWidth(
+    const int &i)
+{
+    BranchItem *bi = getSelectedBranch();
+    if (bi) {
+        BranchObj *bo = (BranchObj *)(bi->getLMO());
+        if (bo) {
+            saveState(bi,
+                      QString("setFrameBorderWidth (\"%1\")")
+                          .arg(bo->getFrameBorderWidth()),
+                      bi, QString("setFrameBorderWidth (\"%1\")").arg(i),
+                      QString("set border width of frame to %1").arg(i));
+            bo->setFrameBorderWidth(i);
+            reposition();
+            bo->updateLinkGeometry();
+        }
+    }
+}
+
+void VymModel::setIncludeImagesVer(bool b)
+{
+    BranchItem *bi = getSelectedBranch();
+    if (bi && b != bi->getIncludeImagesVer()) {
+        QString u = b ? "false" : "true";
+        QString r = !b ? "false" : "true";
+
+        saveState(
+            bi, QString("setIncludeImagesVertically (%1)").arg(u), bi,
+            QString("setIncludeImagesVertically (%1)").arg(r),
+            QString("Include images vertically in %1").arg(getObjectName(bi)));
+        bi->setIncludeImagesVer(b);
+        emitDataChanged(bi);
+        reposition();
+    }
+}
+
+void VymModel::setIncludeImagesHor(bool b)
+{
+    BranchItem *bi = getSelectedBranch();
+    if (bi && b != bi->getIncludeImagesHor()) {
+        QString u = b ? "false" : "true";
+        QString r = !b ? "false" : "true";
+
+        saveState(bi, QString("setIncludeImagesHorizontally (%1)").arg(u), bi,
+                  QString("setIncludeImagesHorizontally (%1)").arg(r),
+                  QString("Include images horizontally in %1")
+                      .arg(getObjectName(bi)));
+        bi->setIncludeImagesHor(b);
+        emitDataChanged(bi);
+        reposition();
+    }
+}
+
+void VymModel::setChildrenLayout(
+    BranchItem::LayoutHint layoutHint) // FIXME-3 no savestate yet
+{
+    BranchItem *bi = getSelectedBranch();
+    if (bi) {
+        /*
+        QString u= b ? "false" : "true";
+        QString r=!b ? "false" : "true";
+
+        saveState(
+            bi,
+            QString("setIncludeImagesHorizontally (%1)").arg(u),
+            bi,
+            QString("setIncludeImagesHorizontally (%1)").arg(r),
+            QString("Include images horizontally in %1").arg(getObjectName(bi))
+        );
+        */
+        bi->setChildrenLayout(layoutHint);
+        emitDataChanged(bi);
+        reposition();
+    }
+}
+
+void VymModel::setHideLinkUnselected(bool b)
+{
+    TreeItem *ti = getSelectedItem();
+    if (ti && (ti->getType() == TreeItem::Image || ti->isBranchLikeType())) {
+        QString u = b ? "false" : "true";
+        QString r = !b ? "false" : "true";
+
+        saveState(
+            ti, QString("setHideLinkUnselected (%1)").arg(u), ti,
+            QString("setHideLinkUnselected (%1)").arg(r),
+            QString("Hide link of %1 if unselected").arg(getObjectName(ti)));
+        ((MapItem *)ti)->setHideLinkUnselected(b);
+    }
+}
+
+void VymModel::setHideExport(bool b, TreeItem *ti)
+{
+    if (!ti)
+        ti = getSelectedItem();
+    if (ti && (ti->getType() == TreeItem::Image || ti->isBranchLikeType()) &&
+        ti->hideInExport() != b) {
+        ti->setHideInExport(b);
+        QString u = b ? "false" : "true";
+        QString r = !b ? "false" : "true";
+
+        saveState(ti, QString("setHideExport (%1)").arg(u), ti,
+                  QString("setHideExport (%1)").arg(r),
+                  QString("Set HideExport flag of %1 to %2")
+                      .arg(getObjectName(ti))
+                      .arg(r));
+        emitDataChanged(ti);
+        emitSelectionChanged();
+        reposition();
+    }
+}
+
+void VymModel::toggleHideExport()
+{
+    QList<TreeItem *> selItems = getSelectedItems();
+    if (selItems.count() > 0) {
+        foreach (TreeItem *ti, selItems) {
+            bool b = !ti->hideInExport();
+            setHideExport(b, ti);
+        }
+    }
+}
+
+void VymModel::toggleTask()
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        saveStateChangingPart(
+            selbi, selbi, QString("toggleTask()"),
+            QString("Toggle task of %1").arg(getObjectName(selbi)));
+        Task *task = selbi->getTask();
+        if (!task) {
+            task = taskModel->createTask(selbi);
+            taskEditor->select(task);
+        }
+        else
+            taskModel->deleteTask(task);
+
+        emitDataChanged(selbi);
+        emitSelectionChanged();
+        reposition();
+    }
+}
+
+bool VymModel::cycleTaskStatus(bool reverse)
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        Task *task = selbi->getTask();
+        if (task) {
+            saveStateChangingPart(
+                selbi, selbi, QString("cycleTask()"),
+                QString("Toggle task of %1").arg(getObjectName(selbi)));
+            task->cycleStatus(reverse);
+            task->setDateModification();
+
+            // make sure task is still visible
+            taskEditor->select(task);
+            emitDataChanged(selbi);
+            reposition();
+            return true;
+        }
+    }
+    return false;
+}
+
+bool VymModel::setTaskSleep(const QString &s)
+{
+    bool ok = false;
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi && !s.isEmpty()) {
+        Task *task = selbi->getTask();
+        if (task) {
+            QDateTime oldSleep = task->getSleep();
+
+            // Parse the string, which could be days, hours or one of several
+            // time formats
+
+            if (s == "0") {
+                ok = task->setSecsSleep(0);
+            }
+            else {
+                QRegExp re("^\\s*(\\d+)\\s*$");
+                re.setMinimal(false);
+                int pos = re.indexIn(s);
+                if (pos >= 0) {
+                    // Found only digit, considered as days
+                    ok = task->setDaysSleep(re.cap(1).toInt());
+                }
+                else {
+                    QRegExp re("^\\s*(\\d+)\\s*h\\s*$");
+                    pos = re.indexIn(s);
+                    if (pos >= 0) {
+                        // Found digit followed by "h", considered as hours
+                        ok = task->setHoursSleep(re.cap(1).toInt());
+                    }
+                    else {
+                        QRegExp re("^\\s*(\\d+)\\s*w\\s*$");
+                        pos = re.indexIn(s);
+                        if (pos >= 0) {
+                            // Found digit followed by "w", considered as weeks
+                            ok = task->setDaysSleep(7 * re.cap(1).toInt());
+                        }
+                        else {
+                            QRegExp re("^\\s*(\\d+)\\s*s\\s*$");
+                            pos = re.indexIn(s);
+                            if (pos >= 0) {
+                                // Found digit followed by "s", considered as
+                                // seconds
+                                ok = task->setSecsSleep(re.cap(1).toInt());
+                            }
+                            else {
+                                ok = task->setDateSleep(
+                                    s); // ISO date YYYY-MM-DDTHH:mm:ss
+
+                                if (!ok) {
+                                    QRegExp re("(\\d+)\\.(\\d+)\\.(\\d+)");
+                                    re.setMinimal(false);
+                                    int pos = re.indexIn(s);
+                                    QStringList list = re.capturedTexts();
+                                    QDateTime d;
+                                    if (pos >= 0) {
+                                        d = QDateTime(
+                                            QDate(list.at(3).toInt(),
+                                                  list.at(2).toInt(),
+                                                  list.at(1).toInt()).startOfDay());
+                                        // d = QDate(list.at(3).toInt(),
+                                        // list.at(2).toInt(),
+                                        // list.at(1).toInt()).startOfDay();
+                                        ok = task->setDateSleep(
+                                            d); // German format,
+                                                // e.g. 24.12.2012
+                                    }
+                                    else {
+                                        re.setPattern("(\\d+)\\.(\\d+)\\.");
+                                        pos = re.indexIn(s);
+                                        list = re.capturedTexts();
+                                        if (pos >= 0) {
+                                            int month = list.at(2).toInt();
+                                            int day = list.at(1).toInt();
+                                            int year =
+                                                QDate::currentDate().year();
+                                            d = QDateTime(
+                                                QDate(year, month, day).startOfDay());
+                                            // d = QDate(year, month,
+                                            // day).startOfDay();
+                                            if (QDateTime::currentDateTime()
+                                                    .daysTo(d) < 0) {
+                                                year++;
+                                                d = QDateTime(
+                                                    QDate(year, month, day).startOfDay());
+                                                // d = QDate(year, month,
+                                                // day).startOfDay();
+                                            }
+                                            ok = task->setDateSleep(
+                                                d); // Short German format,
+                                                    // e.g. 24.12.
+                                        }
+                                        else {
+                                            re.setPattern("(\\d+)\\:(\\d+)");
+                                            pos = re.indexIn(s);
+                                            list = re.capturedTexts();
+                                            if (pos >= 0) {
+                                                int hour = list.at(1).toInt();
+                                                int min = list.at(2).toInt();
+                                                d = QDateTime(
+                                                    QDate::currentDate(),
+                                                    QTime(hour, min));
+                                                ok = task->setDateSleep(
+                                                    d); // Time HH:MM
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (ok) {
+                QString oldSleepString;
+                if (oldSleep.isValid())
+                    oldSleepString = oldSleep.toString(Qt::ISODate);
+                else
+                    oldSleepString =
+                        "1970-01-26T00:00:00"; // Some date long ago
+
+                QString newSleepString = task->getSleep().toString(Qt::ISODate);
+                task->setDateModification();
+                selbi->updateTaskFlag(); // If tasks changes awake mode, then
+                                         // flag needs to change
+                saveState(
+                    selbi, QString("setTaskSleep (\"%1\")").arg(oldSleepString),
+                    selbi, QString("setTaskSleep (\"%1\")").arg(newSleepString),
+                    QString("setTaskSleep (\"%1\")").arg(newSleepString));
+                emitDataChanged(selbi);
+                reposition();
+            }
+
+        } // Found task
+    }     // Found branch
+    return ok;
+}
+
+void VymModel::setTaskPriorityDelta(const int &pd, BranchItem *bi)
+{
+    QList<BranchItem *> selbis;
+    if (bi)
+        selbis << bi;
+    else
+        selbis = getSelectedBranches();
+
+    foreach (BranchItem *selbi, selbis) {
+        Task *task = selbi->getTask();
+        if (task) {
+            saveState(selbi,
+                      QString("setTaskPriorityDelta (%1)")
+                          .arg(task->getPriorityDelta()),
+                      selbi,
+                      QString("setTaskPriorityDelta (%1)")
+                          .arg(pd),
+                      "Set delta for priority of task");
+            task->setPriorityDelta(pd);
+            emitDataChanged(selbi);
+        }
+    }
+}
+
+int VymModel::getTaskPriorityDelta()
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        Task *task = selbi->getTask();
+        if (task)
+            return task->getPriorityDelta();
+    }
+    return 0;
+}
+
+int VymModel::taskCount() { return taskModel->count(this); }
+
+void VymModel::updateTasksAlarm(bool force)
+{
+    if (taskModel->updateAwake(force) || force) {
+        reposition();
+    }
+}
+
+BranchItem *VymModel::addTimestamp()
+{
+    BranchItem *selbi = addNewBranch();
+    if (selbi) {
+        QDate today = QDate::currentDate();
+        QChar c = '0';
+        selbi->setHeadingPlainText(QString("%1-%2-%3")
+                                       .arg(today.year(), 4, 10, c)
+                                       .arg(today.month(), 2, 10, c)
+                                       .arg(today.day(), 2, 10, c));
+        emitDataChanged(selbi);
+        reposition();
+        select(selbi);
+    }
+    return selbi;
+}
+
+void VymModel::copy()
+{
+    if (readonly)
+        return;
+
+    QList<TreeItem *> itemList = getSelectedItems();
+
+    QStringList clipboardFiles;
+
+    if (itemList.count() > 0) {
+        uint i = 1;
+        QString fn;
+        foreach (TreeItem *ti, itemList) {
+            fn = QString("%1/%2-%3.xml")
+                     .arg(clipboardDir)
+                     .arg(clipboardFile)
+                     .arg(i);
+            QString content = saveToDir(clipboardDir, clipboardFile,
+                                        FlagRowMaster::NoFlags, QPointF(), ti);
+
+            if (!saveStringToDisk(fn, content))
+                qWarning() << "ME::saveStringToDisk failed: " << fn;
+            else {
+                i++;
+                clipboardFiles.append(fn);
+            }
+        }
+        QClipboard *clipboard = QApplication::clipboard();
+        QMimeData *mimeData = new QMimeData;
+        mimeData->setData("application/x-vym", clipboardFiles.join(",").toLatin1());
+        clipboard->setMimeData(mimeData);
+    }
+}
+
+void VymModel::paste()
+{
+    if (readonly)
+        return;
+
+    BranchItem *selbi = getSelectedBranch();
+
+    if (selbi) {
+        const QClipboard *clipboard = QApplication::clipboard();
+        const QMimeData *mimeData = clipboard->mimeData();
+
+        if (mimeData->formats().contains("application/x-vym")) {
+            QStringList clipboardFiles = QString(mimeData->data("application/x-vym")).split(",");
+
+            saveStateChangingPart(selbi, selbi, QString("paste ()"),
+                                  QString("Paste"));
+
+            bool zippedOrg = zipped;
+            foreach(QString fn, clipboardFiles) {
+                if (File::Success != loadMap(fn, ImportAdd, VymMap, SlideContent))
+                    qWarning() << "VM::paste Loading clipboard failed: " << fn;
+            }
+            zipped = zippedOrg;
+            reposition();
+        } else if (mimeData->hasImage()) {
+            QImage image = qvariant_cast<QImage>(mimeData->imageData());
+            QString fn = clipboardDir + "/" + "image.png";
+            if (!image.save(fn))
+                qWarning() << "VM::paste  Could not save copy of image in system clipboard";
+            else {
+                ImageItem *ii = loadImage(selbi, fn);
+                if (ii)
+                    setScaleFactor(300.0 / image.width(), ii);    // FIXME-2 Better use user-defined fixed width
+            }
+        } else if (mimeData->hasHtml()) {
+            //setText(mimeData->html());
+            //setTextFormat(Qt::RichText);
+            qDebug() << "VM::paste found html...";
+        } else if (mimeData->hasText()) {
+            //setText(mimeData->text());
+            //setTextFormat(Qt::PlainText);
+            qDebug() << "VM::paste found text...";
+        } else {
+            qWarning() << "VM::paste Cannot paste data, mimeData->formats=" << mimeData->formats();
+        }
+    }
+}
+
+void VymModel::cut()
+{
+    if (readonly)
+        return;
+
+    copy();
+    deleteSelection();
+}
+
+bool VymModel::moveUp(BranchItem *bi)
+{
+    if (readonly)
+        return false;
+
+    bool oldState = saveStateBlocked;
+    saveStateBlocked = true;
+    bool result = false;
+    if (bi && bi->canMoveUp())
+        result =
+            relinkBranch(bi, (BranchItem *)bi->parent(), bi->num() - 1, false);
+    saveStateBlocked = oldState;
+    return result;
+}
+
+void VymModel::moveUp()
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        QString oldsel = getSelectString(selbi);
+        if (moveUp(selbi)) {
+            saveState(getSelectString(selbi), "moveDown ()", oldsel,
+                      "moveUp ()",
+                      QString("Move up %1").arg(getObjectName(selbi)));
+            select(selbi);
+        }
+    }
+}
+
+bool VymModel::moveDown(BranchItem *bi)
+{
+    if (readonly)
+        return false;
+
+    bool oldState = saveStateBlocked;
+    saveStateBlocked = true;
+    bool result = false;
+    if (bi && bi->canMoveDown())
+        result =
+            relinkBranch(bi, (BranchItem *)bi->parent(), bi->num() + 1, false);
+    saveStateBlocked = oldState;
+    return result;
+}
+
+void VymModel::moveDown()
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        QString oldsel = getSelectString(selbi);
+        if (moveDown(selbi)) {
+            saveState(getSelectString(selbi), "moveUp ()", oldsel,
+                      "moveDown ()",
+                      QString("Move down %1").arg(getObjectName(selbi)));
+            select(selbi);
+        }
+    }
+}
+
+void VymModel::moveUpDiagonally()
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        BranchItem *parent = selbi->parentBranch();
+        if (parent == rootItem) return;
+
+        int n = selbi->num();
+        if (n == 0) return;
+
+        BranchItem *dst = parent->getBranchNum(n-1);
+        if (!dst) return;
+
+        relinkBranch(selbi, dst, dst->branchCount() + 1, true);
+     }
+}
+
+void VymModel::moveDownDiagonally()
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        BranchItem *parent = selbi->parentBranch();
+        if (parent == rootItem) return;
+        BranchItem *parentParent = parent->parentBranch();
+        int n = parent->num();
+
+        relinkBranch(selbi, parentParent, n + 1, true);
+     }
+}
+
+void VymModel::detach()
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi && selbi->depth() > 0) {
+        // if no relPos have been set before, try to use current rel positions
+        if (selbi->getLMO())
+            for (int i = 0; i < selbi->branchCount(); ++i)
+                selbi->getBranchNum(i)->getBranchObj()->setRelPos();
+
+        QString oldsel = getSelectString();
+        int n = selbi->num();
+        QPointF p;
+        BranchObj *bo = selbi->getBranchObj();
+        if (bo)
+            p = bo->getAbsPos();
+        QString parsel = getSelectString(selbi->parent());
+        if (relinkBranch(selbi, rootItem, -1, true))
+            saveState(getSelectString(selbi),
+                      QString("relinkTo (\"%1\",%2,%3,%4)")
+                          .arg(parsel)
+                          .arg(n)
+                          .arg(p.x())
+                          .arg(p.y()),
+                      oldsel, "detach ()",
+                      QString("Detach %1").arg(getObjectName(selbi)));
+    }
+}
+
+void VymModel::sortChildren(bool inverse)
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        if (selbi->branchCount() > 1) {
+            if (!inverse)
+                saveStateChangingPart(
+                    selbi, selbi, "sortChildren ()",
+                    QString("Sort children of %1").arg(getObjectName(selbi)));
+            else
+                saveStateChangingPart(selbi, selbi, "sortChildren (false)",
+                                      QString("Inverse sort children of %1")
+                                          .arg(getObjectName(selbi)));
+
+            selbi->sortChildren(inverse);
+            select(selbi);
+            reposition();
+        }
+    }
+}
+
+BranchItem *VymModel::createMapCenter()
+{
+    BranchItem *newbi = addMapCenter(QPointF(0, 0));
+    return newbi;
+}
+
+BranchItem *VymModel::createBranch(BranchItem *dst)
+{
+    if (dst)
+        return addNewBranchInt(dst, -2);
+    else
+        return NULL;
+}
+
+ImageItem *VymModel::createImage(BranchItem *dst)
+{
+    if (dst) {
+        QModelIndex parix;
+        int n;
+
+        ImageItem *newii = new ImageItem();
+        // newii->setHeading (QApplication::translate("Heading of new image in
+        // map", "new image"));
+
+        emit(layoutAboutToBeChanged());
+
+        parix = index(dst);
+        if (!parix.isValid())
+            qDebug() << "VM::createII invalid index\n";
+        n = dst->getRowNumAppend(newii);
+        beginInsertRows(parix, n, n);
+        dst->appendChild(newii);
+        endInsertRows();
+
+        emit(layoutChanged());
+
+        // save scroll state. If scrolled, automatically select
+        // new branch in order to tmp unscroll parent...
+        newii->createMapObj();
+        latestAddedItem = newii;
+        reposition();
+        return newii;
+    }
+    return NULL;
+}
+
+bool VymModel::createLink(Link *link)
+{
+    BranchItem *begin = link->getBeginBranch();
+    BranchItem *end = link->getEndBranch();
+
+    if (!begin || !end) {
+        qWarning() << "VM::createXLinkNew part of XLink is NULL";
+        return false;
+    }
+
+    if (begin == end) {
+        if (debug)
+            qDebug() << "VymModel::createLink begin==end, aborting";
+        return false;
+    }
+
+    // check, if link already exists
+    foreach (Link *l, xlinks) {
+        if ((l->getBeginBranch() == begin && l->getEndBranch() == end) ||
+            (l->getBeginBranch() == end && l->getEndBranch() == begin)) {
+            qWarning() << "VymModel::createLink link exists already, aborting";
+            return false;
+        }
+    }
+
+    QModelIndex parix;
+    int n;
+
+    XLinkItem *newli = new XLinkItem();
+    newli->setLink(link);
+    link->setBeginLinkItem(newli);
+
+    emit(layoutAboutToBeChanged());
+
+    parix = index(begin);
+    n = begin->getRowNumAppend(newli);
+    beginInsertRows(parix, n, n);
+    begin->appendChild(newli);
+    endInsertRows();
+
+    newli = new XLinkItem();
+    newli->setLink(link);
+    link->setEndLinkItem(newli);
+
+    parix = index(end);
+    n = end->getRowNumAppend(newli);
+    beginInsertRows(parix, n, n);
+    end->appendChild(newli);
+    endInsertRows();
+
+    emit(layoutChanged());
+
+    xlinks.append(link);
+    link->activate();
+
+    latestAddedItem = newli;
+
+    if (!link->getMO()) {
+        link->createMapObj();
+        reposition();
+    }
+    else
+        link->updateLink();
+
+    link->setStyleBegin(defXLinkStyleBegin);
+    link->setStyleEnd(defXLinkStyleEnd);
+    return true;
+}
+
+QColor VymModel::getXLinkColor()
+{
+    Link *l = getSelectedXLink();
+    if (l)
+        return l->getPen().color();
+    else
+        return QColor();
+}
+
+int VymModel::getXLinkWidth()
+{
+    Link *l = getSelectedXLink();
+    if (l)
+        return l->getPen().width();
+    else
+        return -1;
+}
+
+Qt::PenStyle VymModel::getXLinkStyle()
+{
+    Link *l = getSelectedXLink();
+    if (l)
+        return l->getPen().style();
+    else
+        return Qt::NoPen;
+}
+
+QString VymModel::getXLinkStyleBegin()
+{
+    Link *l = getSelectedXLink();
+    if (l)
+        return l->getStyleBeginString();
+    else
+        return QString();
+}
+
+QString VymModel::getXLinkStyleEnd()
+{
+    Link *l = getSelectedXLink();
+    if (l)
+        return l->getStyleEndString();
+    else
+        return QString();
+}
+
+AttributeItem *VymModel::setAttribute() // FIXME-3 Experimental, savestate missing
+
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        AttributeItem *ai = new AttributeItem();
+        ai->setAttributeType(AttributeItem::String);
+        ai->setKey("Foo Attrib");
+        ai->setValue(QString("Att val"));
+
+        return setAttribute(selbi, ai);
+    }
+    return nullptr;
+}
+
+AttributeItem *VymModel::setAttribute(BranchItem *dst, AttributeItem *ai_new)
+{
+    if (dst) {
+
+        // Check if there is already an attribute with same key
+        AttributeItem *ai;
+        for (int i = 0; i < dst->attributeCount(); i++) {
+            ai = dst->getAttributeNum(i);
+            if (ai->getKey() == ai_new->getKey()) 
+            {
+                // Key exists, overwrite value
+                ai->copy(ai_new);
+
+                // Delete original attribute, this is basically a move...
+                delete ai_new;
+                emitDataChanged(dst);
+                return ai;
+            }
+        }
+
+        // Create new attribute
+        emit(layoutAboutToBeChanged());
+
+        QModelIndex parix = index(dst);
+        int n = dst->getRowNumAppend(ai_new);
+        beginInsertRows(parix, n, n);
+        dst->appendChild(ai_new);
+        endInsertRows();
+
+        emit(layoutChanged());
+
+        emitDataChanged(dst);
+        return ai_new;  // FIXME-3 Check if ai is used or deleted - deep copy here?
+    }
+    return NULL;
+}
+
+BranchItem *VymModel::addMapCenter(bool saveStateFlag)
+{
+    if (!hasContextPos) {
+        // E.g. when called via keypresss:
+        // Place new MCO in middle of existing ones,
+        // Useful for "brainstorming" mode...
+        contextPos = QPointF();
+        BranchItem *bi;
+        BranchObj *bo;
+        for (int i = 0; i < rootItem->branchCount(); ++i) {
+            bi = rootItem->getBranchNum(i);
+            bo = (BranchObj *)bi->getLMO();
+            if (bo)
+                contextPos += bo->getAbsPos();
+        }
+        if (rootItem->branchCount() > 1)
+            contextPos *= 1 / (qreal)(rootItem->branchCount());
+    }
+
+    BranchItem *bi = addMapCenter(contextPos);
+    updateActions();
+    emitShowSelection();
+    if (saveStateFlag)
+        saveState(bi, "remove()", NULL,
+                  QString("addMapCenter (%1,%2)")
+                      .arg(contextPos.x())
+                      .arg(contextPos.y()),
+                  QString("Adding MapCenter to (%1,%2)")
+                      .arg(contextPos.x())
+                      .arg(contextPos.y()));
+    emitUpdateLayout();
+    return bi;
+}
+
+BranchItem *VymModel::addMapCenter(QPointF absPos)
+// createMapCenter could then probably be merged with createBranch
+{
+
+    // Create TreeItem
+    QModelIndex parix = index(rootItem);
+
+    BranchItem *newbi = new BranchItem(rootItem);
+    newbi->setHeadingPlainText(tr("New map", "New map"));
+    int n = rootItem->getRowNumAppend(newbi);
+
+    emit(layoutAboutToBeChanged());
+    beginInsertRows(parix, n, n);
+
+    rootItem->appendChild(newbi);
+
+    endInsertRows();
+    emit(layoutChanged());
+
+    // Create MapObj
+    newbi->setPositionMode(MapItem::Absolute);
+    BranchObj *bo = newbi->createMapObj(mapEditor->getScene());
+    if (bo)
+        bo->move(absPos);
+
+    return newbi;
+}
+
+BranchItem *VymModel::addNewBranchInt(BranchItem *dst, int pos)
+{
+    // Depending on pos:
+    // -3      insert in children of parent  above selection
+    // -2      add branch to selection
+    // -1      insert in children of parent below selection
+    // 0..n    insert in children of parent at pos
+
+    // Create TreeItem
+    BranchItem *parbi = dst;
+    int n;
+    BranchItem *newbi = new BranchItem();
+
+    emit(layoutAboutToBeChanged());
+
+    if (pos == -2) {
+        n = parbi->getRowNumAppend(newbi);
+        beginInsertRows(index(parbi), n, n);
+        parbi->appendChild(newbi);
+        endInsertRows();
+    }
+    else if (pos == -1 || pos == -3) {
+        // insert below selection
+        parbi = (BranchItem *)dst->parent();
+        n = dst->childNumber() + (3 + pos) / 2; //-1 |-> 1;-3 |-> 0
+        beginInsertRows(index(parbi), n, n);
+        parbi->insertBranch(n, newbi);
+        endInsertRows();
+    }
+    else { // pos >= 0
+        n = parbi->getRowNumAppend(newbi) - (parbi->branchCount() - pos);
+        beginInsertRows(index(parbi), n, n);
+        parbi->insertBranch(pos, newbi);
+        endInsertRows();
+    }
+    emit(layoutChanged());
+
+    newbi->createMapObj(mapEditor->getScene());
+
+    // Set color of heading to that of parent
+    newbi->setHeadingColor(parbi->getHeadingColor());
+
+    reposition();
+    return newbi;
+}
+
+BranchItem *VymModel::addNewBranch(BranchItem *bi, int pos)
+{
+    BranchItem *newbi = NULL;
+    if (!bi)
+        bi = getSelectedBranch();
+
+    if (bi) {
+        QString redosel = getSelectString(bi);
+        newbi = addNewBranchInt(bi, pos);
+        QString undosel = getSelectString(newbi);
+
+        if (newbi) {
+            saveState(undosel, "remove ()", redosel,
+                      QString("addBranch (%1)").arg(pos),
+                      QString("Add new branch to %1").arg(getObjectName(bi)));
+
+            latestAddedItem = newbi;
+            // In Network mode, the client needs to know where the new branch
+            // is, so we have to pass on this information via saveState.
+            // TODO: Get rid of this positioning workaround
+            /* FIXME-4  network problem:  QString ps=qpointfToString
+               (newbo->getAbsPos()); sendData ("selectLatestAdded ()"); sendData
+               (QString("move %1").arg(ps)); sendSelection();
+               */
+        }
+    }
+    return newbi;
+}
+
+BranchItem *VymModel::addNewBranchBefore()
+{
+    BranchItem *newbi = NULL;
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi && selbi->getType() == TreeItem::Branch)
+    // We accept no MapCenter here, so we _have_ a parent
+    {
+        // add below selection
+        newbi = addNewBranchInt(selbi, -1);
+
+        if (newbi) {
+            saveState(
+                newbi, "remove ()", newbi, "addBranchBefore ()",
+                QString("Add branch before %1").arg(getObjectName(selbi)));
+
+            // newbi->move2RelPos (p);
+
+            // Move selection to new branch
+            relinkBranch(selbi, newbi, 0, true);
+
+            // Use color of child instead of parent
+            newbi->setHeadingColor(selbi->getHeadingColor());
+            emitDataChanged(newbi);
+        }
+    }
+    return newbi;
+}
+
+bool VymModel::relinkBranch(BranchItem *branch, BranchItem *dst, int pos,
+                            bool updateSelection, QPointF orgPos)
+{
+    if (branch && dst) {
+        // Check if we relink to ourselves
+        if (dst->isChildOf(branch))
+            return false;
+
+        if (updateSelection)
+            unselectAll();
+
+        // Do we need to update frame type?
+        bool keepFrame = true;
+
+        // Save old position for savestate
+        QString preSelStr = getSelectString(branch);
+        QString preNum = QString::number(branch->num(), 10);
+        QString preParStr = getSelectString(branch->parent());
+
+        emit(layoutAboutToBeChanged());
+        BranchItem *branchpi = (BranchItem *)branch->parent();
+        // Remove at current position
+        int n = branch->childNum();
+
+        // If branch and dst have same parent, then pos needs to be adjusted 
+        // after removing branch
+        if (branchpi == dst && pos - 1 > n ) pos--;
+
+        beginRemoveRows(index(branchpi), n, n);
+        branchpi->removeChild(n);
+        endRemoveRows();
+
+        if (pos < 0 || pos > dst->branchCount())
+            pos = dst->branchCount();
+
+        // Append as last branch to dst
+        if (dst->branchCount() == 0)
+            n = 0;
+        else
+            n = dst->getFirstBranch()->childNumber();
+        
+        beginInsertRows(index(dst), n + pos, n + pos);
+        dst->insertBranch(pos, branch);
+        endInsertRows();
+
+        // Correct type if necessesary
+        if (branch->getType() == TreeItem::MapCenter && branch->depth() > 0) {
+            branch->setType(TreeItem::Branch);
+            keepFrame = false;
+        }
+
+        // reset parObj, fonts, frame, etc in related LMO or other view-objects
+        branch->updateStyles(keepFrame);
+
+        emitDataChanged(branch);
+        reposition(); // both for moveUp/Down and relinking
+
+        // Savestate
+        QString postSelStr = getSelectString(branch);
+        QString postNum = QString::number(branch->num(), 10);
+
+        QPointF savePos;
+        LinkableMapObj *lmosel = branch->getLMO();
+        if (lmosel)
+            savePos = lmosel->getAbsPos();
+
+        if (!saveStateBlocked) { // Don't build strings when moving up/down
+            QString undoCom =
+                "relinkTo (\"" + preParStr + "\"," + preNum + "," +
+                QString("%1,%2").arg(orgPos.x()).arg(orgPos.y()) + ")";
+
+            QString redoCom =
+                "relinkTo (\"" + getSelectString(dst) + "\"," + postNum + "," +
+                QString("%1,%2").arg(savePos.x()).arg(savePos.y()) + ")";
+
+            saveState(postSelStr, undoCom, preSelStr, redoCom,
+                      QString("Relink %1 to %2")
+                          .arg(getObjectName(branch))
+                          .arg(getObjectName(dst)));
+        }
+
+        // New parent might be invisible
+        branch->updateVisibility();
+
+        if (dst->isScrolled()) {
+            if (updateSelection)
+                select(dst);
+        }
+        else if (updateSelection)
+            select(branch);
+        return true;
+    }
+    return false;
+}
+
+bool VymModel::relinkImage(ImageItem *image, BranchItem *dst)
+{
+    if (image && dst) {
+        emit(layoutAboutToBeChanged());
+
+        BranchItem *pi = (BranchItem *)(image->parent());
+        QString oldParString = getSelectString(pi);
+        // Remove at current position
+        int n = image->childNum();
+        beginRemoveRows(index(pi), n, n);
+        pi->removeChild(n);
+        endRemoveRows();
+
+        // Add at dst
+        QModelIndex dstix = index(dst);
+        n = dst->getRowNumAppend(image);
+        beginInsertRows(dstix, n, n);
+        dst->appendChild(image);
+        endInsertRows();
+
+        // Set new parent also for lmo
+        if (image->getLMO() && dst->getLMO())
+            image->getLMO()->setParObj(dst->getLMO());
+
+        emit(layoutChanged());
+        saveState(image, QString("relinkTo (\"%1\")").arg(oldParString), image,
+                  QString("relinkTo (\"%1\")").arg(getSelectString(dst)),
+                  QString("Relink floatimage to %1").arg(getObjectName(dst)));
+        return true;
+    }
+    return false;
+}
+
+bool VymModel::relinkTo(const QString &dest, int num, QPointF pos)
+{
+    TreeItem *selti = getSelectedItem();
+    if (!selti)
+        return false; // Nothing selected to relink
+
+    TreeItem *dst = findBySelectString(dest);
+
+    if (selti->isBranchLikeType()) {
+        BranchItem *selbi = (BranchItem *)selti;
+        if (!dst)
+            return false; // Could not find destination
+
+        if (dst->getType() == TreeItem::Branch) {
+            // Now try to relink to branch
+            if (relinkBranch(selbi, (BranchItem *)dst, num, true)) {
+                emitSelectionChanged();
+                return true;
+            }
+            else
+                return false; // Relinking failed
+        }
+        else if (dst->getType() == TreeItem::MapCenter) {
+            if (relinkBranch(selbi, (BranchItem *)dst, -1, true)) {
+                // Get coordinates of mainbranch
+                if (selbi->getLMO()) {
+                    ((BranchObj *)selbi->getLMO())->move(pos);
+                    ((BranchObj *)selbi->getLMO())->setRelPos();
+                }
+                reposition();
+                emitSelectionChanged();
+                return true;
+            }
+        }
+        return false; // Relinking failed
+    }
+    else if (selti->getType() == TreeItem::Image) {
+        if (dst->isBranchLikeType())
+            if (relinkImage(((ImageItem *)selti), (BranchItem *)dst))
+                return true;
+    }
+    return false; // Relinking failed
+}
+
+void VymModel::cleanupItems()
+{
+    while (!deleteLaterIDs.isEmpty()) {
+        TreeItem *ti = findID(deleteLaterIDs.takeFirst());
+        if (ti)
+            deleteItem(ti);
+    }
+}
+
+void VymModel::deleteLater(uint id)
+{
+    if (!deleteLaterIDs.contains(id))
+        deleteLaterIDs.append(id);
+}
+
+void VymModel::deleteSelection()
+{
+    QList<uint> selectedIDs = getSelectedIDs();
+    unselectAll();
+    QString fn;
+
+    foreach (uint id, selectedIDs) {
+        TreeItem *ti = findID(id);
+        if (ti) {
+            if (ti->isBranchLikeType()) { // Delete branch
+                BranchItem *selbi = (BranchItem *)ti;
+                saveStateRemovingPart(
+                    selbi, QString("remove %1").arg(getObjectName(selbi)));
+
+                BranchItem *pi = (BranchItem *)(deleteItem(selbi));
+                if (pi) {
+                    if (pi->isScrolled() && pi->branchCount() == 0)
+                        pi->unScroll();
+                    emitDataChanged(pi);
+                    select(pi);
+                }
+                else
+                    emitDataChanged(rootItem);
+                ti = NULL;
+            }
+            else {
+                // Delete other item
+                TreeItem *pi = ti->parent();
+                if (pi) {
+                    if (ti->getType() == TreeItem::Image ||
+                        ti->getType() == TreeItem::Attribute ||
+                        ti->getType() == TreeItem::XLink) {
+                        saveStateChangingPart(
+                            pi, ti, "remove ()",
+                            QString("Remove %1").arg(getObjectName(ti)));
+
+                        deleteItem(ti);
+                        emitDataChanged(pi);
+                        select(pi);
+                        reposition();
+                    }
+                    else
+                        qWarning(
+                            "VymmModel::deleteSelection()  unknown type?!");
+                }
+            }
+        }
+    }
+}
+
+void VymModel::deleteKeepChildren(bool saveStateFlag)
+// deleteKeepChildren FIXME-3+ does not work yet for mapcenters
+// deleteKeepChildren FIXME-3+ children of scrolled branch stay invisible...
+{
+    BranchItem *selbi = getSelectedBranch();
+    BranchItem *pi;
+    if (selbi) {
+        // Don't use this on mapcenter
+        if (selbi->depth() < 1)
+            return;
+
+        pi = (BranchItem *)(selbi->parent());
+        // Check if we have children at all to keep
+        if (selbi->branchCount() == 0) {
+            deleteSelection();
+            return;
+        }
+
+        QPointF p;
+        if (selbi->getLMO())
+            p = selbi->getLMO()->getRelPos();
+        if (saveStateFlag)
+            saveStateChangingPart(pi, pi, "removeKeepChildren ()",
+                                  QString("Remove %1 and keep its children")
+                                      .arg(getObjectName(selbi)));
+
+        QString sel = getSelectString(selbi);
+        unselectAll();
+        bool oldSaveState = saveStateBlocked;
+        saveStateBlocked = true;
+        int pos = selbi->num();
+        BranchItem *bi = selbi->getFirstBranch();
+        while (bi) {
+            relinkBranch(bi, pi, pos, true);
+            bi = selbi->getFirstBranch();
+            pos++;
+        }
+        deleteItem(selbi);
+        reposition();
+        emitDataChanged(pi);
+        select(sel);
+        BranchObj *bo = getSelectedBranchObj();
+        if (bo) {
+            bo->move2RelPos(p);
+            reposition();
+        }
+        saveStateBlocked = oldSaveState;
+    }
+}
+
+void VymModel::deleteChildren()
+
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        saveStateChangingPart(
+            selbi, selbi, "removeChildren ()",
+            QString("Remove children of branch %1").arg(getObjectName(selbi)));
+        emit(layoutAboutToBeChanged());
+
+        QModelIndex ix = index(selbi);
+        int n = selbi->branchCount() - 1;
+        beginRemoveRows(ix, 0, n);
+        removeRows(0, n + 1, ix);
+        endRemoveRows();
+        if (selbi->isScrolled())
+            unscrollBranch(selbi);
+        emit(layoutChanged());
+        reposition();
+    }
+}
+
+TreeItem *VymModel::deleteItem(TreeItem *ti)
+{
+    if (ti) {
+        TreeItem *pi = ti->parent();
+        // qDebug()<<"VM::deleteItem  start ti="<<ti<<"  "<<ti->getHeading()<<"
+        // pi="<<pi<<"="<<pi->getHeading();
+
+        TreeItem::Type t = ti->getType();
+
+        QModelIndex parentIndex = index(pi);
+
+        emit(layoutAboutToBeChanged());
+
+        int n = ti->childNum();
+        beginRemoveRows(parentIndex, n, n);
+        removeRows(n, 1, parentIndex);
+        endRemoveRows();
+
+        // Size of parent branch might change when deleting images
+        if (t == TreeItem::Image) {
+            BranchObj *bo = (BranchObj *)(((BranchItem *)pi)->getMO());
+            if (bo)
+                bo->calcBBoxSize();
+        }
+
+        reposition();
+
+        emit(layoutChanged());
+        emitUpdateQueries();
+        if (!cleaningUpLinks)
+            cleanupItems();
+
+        // qDebug()<<"VM::deleteItem  end   ti="<<ti;
+        if (pi->depth() >= 0)
+            return pi;
+    }
+    return NULL;
+}
+
+void VymModel::deleteLink(Link *l)
+{
+    if (xlinks.removeOne(l))
+        delete (l);
+}
+
+void VymModel::clearItem(TreeItem *ti)
+{
+    if (ti) {
+        // Clear task (or other data in item itself)
+        ti->clear();
+
+        QModelIndex parentIndex = index(ti);
+        if (!parentIndex.isValid())
+            return;
+
+        int n = ti->childCount();
+        if (n == 0)
+            return;
+
+        emit(layoutAboutToBeChanged());
+
+        beginRemoveRows(parentIndex, 0, n - 1);
+        removeRows(0, n, parentIndex);
+        endRemoveRows();
+
+        reposition();
+
+        emit(layoutChanged());
+    }
+    return;
+}
+
+bool VymModel::scrollBranch(BranchItem *bi)
+{
+    if (bi) {
+        if (bi->isScrolled())
+            return false;
+        if (bi->branchCount() == 0)
+            return false;
+        if (bi->depth() == 0)
+            return false;
+        if (bi->toggleScroll()) {
+            QString u, r;
+            r = "scroll";
+            u = "unscroll";
+            saveState(bi, QString("%1 ()").arg(u), bi, QString("%1 ()").arg(r),
+                      QString("%1 %2").arg(r).arg(getObjectName(bi)));
+            emitDataChanged(bi);
+            emitSelectionChanged();
+            reposition();
+            mapEditor->getScene()
+                ->update(); // Needed for _quick_ update,  even in 1.13.x
+            return true;
+        }
+    }
+    return false;
+}
+
+bool VymModel::unscrollBranch(BranchItem *bi)
+{
+    if (bi) {
+        if (!bi->isScrolled())
+            return false;
+        if (bi->toggleScroll()) {
+            QString u, r;
+            u = "scroll";
+            r = "unscroll";
+            saveState(bi, QString("%1 ()").arg(u), bi, QString("%1 ()").arg(r),
+                      QString("%1 %2").arg(r).arg(getObjectName(bi)));
+            emitDataChanged(bi);
+            emitSelectionChanged();
+            reposition();
+            mapEditor->getScene()
+                ->update(); // Needed for _quick_ update,  even in 1.13.x
+            return true;
+        }
+    }
+    return false;
+}
+
+void VymModel::toggleScroll()
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        if (selbi->isScrolled())
+            unscrollBranch(selbi);
+        else
+            scrollBranch(selbi);
+        // Note: saveState & reposition are called in above functions
+    }
+}
+
+void VymModel::unscrollChildren()
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        saveStateChangingPart(
+            selbi, selbi, QString("unscrollChildren ()"),
+            QString("unscroll all children of %1").arg(getObjectName(selbi)));
+        BranchItem *prev = NULL;
+        BranchItem *cur = NULL;
+        nextBranch(cur, prev, true, selbi);
+        while (cur) {
+            if (cur->isScrolled()) {
+                cur->toggleScroll();
+                emitDataChanged(cur);
+            }
+            nextBranch(cur, prev, true, selbi);
+        }
+        updateActions();
+        reposition();
+        // Would this help??? emitSelectionChanged();
+    }
+}
+
+void VymModel::setScaleFactor(qreal f, ImageItem *selii)
+{
+    if (!selii)
+        selii = getSelectedImage();
+
+    if (selii) {
+        qreal f_old = selii->getScaleFactor();
+        selii->setScaleFactor(f);
+        saveState(selii, QString("setScaleFactor(%1)").arg(f_old), selii,
+                  QString("setScaleFactor(%1)").arg(f),
+                  QString("Scale %1").arg(getObjectName(selii)));
+        reposition();
+    }
+}
+
+void VymModel::growSelectionSize() // FIXME-3 Also for heading in BranchItem?
+{
+    ImageItem *selii = getSelectedImage();
+    if (selii) {
+        qreal f = 0.05;
+        qreal sx = selii->getScaleFactor();
+        setScaleFactor(sx + f);
+    }
+}
+
+void VymModel::shrinkSelectionSize()
+{
+    ImageItem *selii = getSelectedImage();
+    if (selii) {
+        qreal f = 0.05;
+        qreal sx = selii->getScaleFactor();
+        setScaleFactor(sx - f);
+    }
+}
+
+void VymModel::resetSelectionSize()
+{
+    ImageItem *selii = getSelectedImage();
+    if (selii)
+        setScaleFactor(1);
+}
+
+void VymModel::emitExpandAll() { emit(expandAll()); }
+
+void VymModel::emitExpandOneLevel() { emit(expandOneLevel()); }
+
+void VymModel::emitCollapseOneLevel() { emit(collapseOneLevel()); }
+
+void VymModel::emitCollapseUnselected() { emit(collapseUnselected()); }
+
+void VymModel::toggleTarget()
+{
+    foreach (TreeItem *ti, getSelectedItems()) {
+        if (ti->isBranchLikeType()) {
+            ((BranchItem*)ti)->toggleTarget();
+            saveState(ti, "toggleTarget()", ti, "toggleTarget()",
+                      "Toggle target");
+        }
+    }
+    reposition();
+}
+
+ItemList VymModel::getLinkedMaps()
+{
+    ItemList targets;
+
+    // rmodel->setSearchString (s);
+
+    BranchItem *cur = NULL;
+    BranchItem *prev = NULL;
+    nextBranch(cur, prev);
+
+    QString s;
+
+    while (cur) {
+        if (cur->hasActiveSystemFlag("system-target") &&
+            !cur->getVymLink().isEmpty()) {
+            s = cur->getHeading().getTextASCII();
+            s.replace(QRegularExpression("\n+"), " ");
+            s.replace(QRegularExpression("\\s+"), " ");
+            s.replace(QRegularExpression("^\\s+"), "");
+
+            QStringList sl;
+            sl << s;
+            sl << cur->getVymLink();
+
+            targets[cur->getID()] = sl;
+        }
+        nextBranch(cur, prev);
+    }
+    return targets;
+}
+
+ItemList VymModel::getTargets()
+{
+    ItemList targets;
+
+    // rmodel->setSearchString (s);
+
+    BranchItem *cur = NULL;
+    BranchItem *prev = NULL;
+    nextBranch(cur, prev);
+
+    QString s;
+
+    while (cur) {
+        if (cur->hasActiveSystemFlag("system-target")) {
+            s = cur->getHeading().getTextASCII();
+            s.replace(QRegularExpression("\n+"), " ");
+            s.replace(QRegularExpression("\\s+"), " ");
+            s.replace(QRegularExpression("^\\s+"), "");
+
+            QStringList sl;
+            sl << s;
+
+            targets[cur->getID()] = sl;
+        }
+        nextBranch(cur, prev);
+    }
+    return targets;
+}
+
+Flag* VymModel::findFlagByName(const QString &name)
+{
+    BranchItem *bi = getSelectedBranch();
+
+    if (bi) {
+        Flag *f = standardFlagsMaster->findFlagByName(name);
+        if (!f) {
+            f = userFlagsMaster->findFlagByName(name);
+            if (!f) {
+                qWarning() << "VymModel::findFlagByName failed for flag named "
+                           << name;
+                return nullptr;
+            }
+        }
+        return f;
+    }
+
+    // Nothing selected, so no flag found
+    return nullptr;
+}
+
+void VymModel::setFlagByName(const QString &name, bool useGroups)
+{
+    BranchItem *bi = getSelectedBranch();
+
+    if (bi && !bi->hasActiveFlag(name)) {
+        toggleFlagByName(name, useGroups);
+    }
+}
+
+void VymModel::unsetFlagByName(const QString &name)
+{
+    BranchItem *bi = getSelectedBranch();
+
+    if (bi && bi->hasActiveFlag(name)) {
+        toggleFlagByName(name);
+    }
+}
+
+void VymModel::toggleFlagByName(const QString &name, bool useGroups)
+{
+    BranchItem *bi = getSelectedBranch();
+
+    if (bi) {
+        Flag *f = findFlagByName(name);
+
+        if (!f) {
+            qWarning() << "VymModel::toggleFlagByName could not find flag named " << name;
+            return;
+        }
+
+        toggleFlagByUid(f->getUuid(), useGroups);
+    }
+}
+
+void VymModel::toggleFlagByUid(
+    const QUuid &uid,
+    bool useGroups)
+    // FIXME-2  saveState not correct when toggling flags in groups
+    // (previous flags not saved!)
+{
+    QStringList itemList = getSelectedUUIDs();
+
+    if (itemList.count() > 0) {
+        QString fn;
+        TreeItem *ti;
+        BranchItem *bi;
+        Flag *f;
+        foreach (QString id, itemList) {
+            ti = findUuid(QUuid(id));
+            if (ti && ti->isBranchLikeType()) {
+                    bi = (BranchItem*)ti;
+                f = bi->toggleFlagByUid(uid, useGroups);
+
+                if (f) {
+                    QString u = "toggleFlagByUid";
+                    QString name = f->getName();
+                    saveState(bi, QString("%1 (\"%2\")").arg(u).arg(uid.toString()), bi,
+                              QString("%1 (\"%2\")").arg(u).arg(uid.toString()),
+                              QString("Toggling flag \"%1\" of %2")
+                                  .arg(name)
+                                  .arg(getObjectName(bi)));
+                    emitDataChanged(bi);
+                } else
+                    qWarning() << "VymModel::toggleFlag failed for flag with uid "
+                               << uid;
+            }
+        }
+        reposition();
+    }
+}
+
+
+void VymModel::clearFlags()
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        selbi->deactivateAllStandardFlags();
+        reposition();
+        emitDataChanged(selbi);
+        setChanged();
+    }
+}
+
+void VymModel::colorBranch(QColor c)
+{
+    QList<BranchItem *> selbis = getSelectedBranches();
+    foreach (BranchItem *selbi, selbis) {
+        saveState(selbi,
+                  QString("colorBranch (\"%1\")")
+                      .arg(selbi->getHeadingColor().name()),
+                  selbi, QString("colorBranch (\"%1\")").arg(c.name()),
+                  QString("Set color of %1 to %2")
+                      .arg(getObjectName(selbi))
+                      .arg(c.name()));
+        selbi->setHeadingColor(c); // color branch
+        emitDataChanged(selbi);
+        taskEditor->showSelection();
+    }
+    mapEditor->getScene()->update();
+}
+
+void VymModel::colorSubtree(QColor c, BranchItem *b)
+{
+    QList<BranchItem *> selbis;
+    if (b)
+        selbis.append(b);
+    else
+        selbis = getSelectedBranches();
+
+    foreach (BranchItem *bi, selbis) {
+        saveStateChangingPart(bi, bi,
+                              QString("colorSubtree (\"%1\")").arg(c.name()),
+                              QString("Set color of %1 and children to %2")
+                                  .arg(getObjectName(bi))
+                                  .arg(c.name()));
+        BranchItem *prev = NULL;
+        BranchItem *cur = NULL;
+        nextBranch(cur, prev, true, bi);
+        while (cur) {
+            cur->setHeadingColor(c); // color links, color children
+            emitDataChanged(cur);
+            nextBranch(cur, prev, true, bi);
+        }
+    }
+    taskEditor->showSelection();
+    mapEditor->getScene()->update();
+}
+
+QColor VymModel::getCurrentHeadingColor()
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi)
+        return selbi->getHeadingColor();
+
+    QMessageBox::warning(
+        0, "Warning",
+        "Can't get color of heading,\nthere's no branch selected");
+    return Qt::black;
+}
+
+void VymModel::note2URLs()
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        saveStateChangingPart(
+            selbi, selbi, QString("note2URLs()"),
+            QString("Extract URLs from note of %1").arg(getObjectName(selbi)));
+
+        QString n = selbi->getNoteASCII();
+        if (n.isEmpty())
+            return;
+        QRegExp re("(http.*)(\\s|\"|')");
+        re.setMinimal(true);
+
+        BranchItem *bi;
+        int pos = 0;
+        while ((pos = re.indexIn(n, pos)) != -1) {
+            bi = createBranch(selbi);
+            bi->setHeadingPlainText(re.cap(1));
+            bi->setURL(re.cap(1));
+            emitDataChanged(bi);
+            pos += re.matchedLength();
+        }
+    }
+}
+
+void VymModel::editHeading2URL()
+{
+    TreeItem *selti = getSelectedItem();
+    if (selti)
+        setURL(selti->getHeadingPlain());
+}
+
+void VymModel::getJiraData(bool subtree) // FIXME-2 update error message, check
+                                         // if jiraClientAvail is set correctly
+{
+    if (!JiraAgent::available()) {
+        WarningDialog dia;
+        QString w = QObject::tr("JIRA agent not setup.");
+        dia.setText(w);
+        dia.setWindowTitle( tr("Warning") + ": " + w);
+        dia.setShowAgainName("/JiraAgent/notdefined");
+        dia.exec();
+
+        if (!mainWindow->settingsJIRA())
+            return;
+    }
+
+    BranchItem *selbi = getSelectedBranch();
+    QRegExp re("(\\w+[-|\\s]\\d+)");
+
+    if (selbi) {
+        QString url;
+        BranchItem *prev = nullptr;
+        BranchItem *cur = nullptr;
+        nextBranch(cur, prev, true, selbi);
+        while (cur) {
+            QString heading = cur->getHeadingPlain();
+
+            if (re.indexIn(heading) >= 0) {
+                // Create agent
+                JiraAgent *agent = new JiraAgent;
+                agent->setJobType(JiraAgent::GetTicketInfo);
+                if (!agent->setBranch(cur)) {
+                    qWarning () << "Could not set branch in JiraAgent to " << cur->getHeadingPlain();
+                    delete agent;
+                    return;
+                }
+                if (!agent->setTicket(heading)) {
+                    mainWindow->statusMessage(tr("Could not find Jira ticket pattern in %1", "VymModel").arg(cur->getHeadingPlain()));
+                    delete agent;
+                    return;
+                }
+
+                //setURL(agent->url(), false, cur);
+
+                connect(agent, &JiraAgent::jiraTicketReady, this, &VymModel::updateJiraData);
+
+                // Start contacting JIRA in background
+                agent->startJob();
+                mainWindow->statusMessage(tr("Contacting Jira...", "VymModel"));
+            }
+
+
+            if (subtree)
+                nextBranch(cur, prev, true, selbi);
+            else
+                cur = nullptr;
+        }
+    }
+}
+
+void VymModel::updateJiraData(QJsonObject jsobj)
+{
+    QJsonDocument jsdoc = QJsonDocument (jsobj);
+    QString key = jsobj["key"].toString();
+    QJsonObject fields = jsobj["fields"].toObject();
+
+    QJsonObject assigneeObj = fields["assignee"].toObject();
+    QString assignee = assigneeObj["emailAddress"].toString();
+
+    QJsonObject reporterObj = fields["reporter"].toObject();
+    QString reporter  = reporterObj["emailAddress"].toString();
+
+    QJsonObject resolutionObj = fields["resolution"].toObject();
+    QString resolution  = resolutionObj["name"].toString();
+
+    QJsonObject statusObj = fields["status"].toObject();
+    QString status  = statusObj["name"].toString();
+
+    QString summary = fields["summary"].toString();
+
+    QJsonArray componentsArray = fields["components"].toArray();
+    QJsonObject compObj;
+    QString components;
+    for (int i = 0; i < componentsArray.size(); ++i) {
+        compObj = componentsArray[i].toObject();
+        components += compObj["name"].toString();
+    }
+
+    int branchID = jsobj["vymBranchId"].toInt();
+
+    QStringList solvedStates;
+    solvedStates << "Verification Done";
+    solvedStates << "Resolved";
+    solvedStates << "Closed";
+
+    QString keyName = key;
+    BranchItem *bi = (BranchItem*)findID(branchID);
+    if (bi) {
+        if (solvedStates.contains(status))    {
+            keyName = "(" + keyName + ")";
+            colorSubtree (Qt::blue, bi);
+        }
+
+        setHeadingPlainText(keyName + ": " + summary, bi);
+        setURL(jsobj["vymTicketUrl"].toString());
+
+        AttributeItem *ai;
+
+        ai = new AttributeItem("JIRA.assignee", assignee);
+        setAttribute(bi, ai);
+
+        ai = new AttributeItem("JIRA.reporter", reporter);
+        setAttribute(bi, ai);
+
+        ai = new AttributeItem("JIRA.resolution", resolution);
+        setAttribute(bi, ai);
+
+        ai = new AttributeItem("JIRA.status", status);
+        setAttribute(bi, ai);
+
+        ai = new AttributeItem("JIRA.components", components);
+        setAttribute(bi, ai);
+    }
+
+    /* Pretty print JIRA ticket
+    vout << jsdoc.toJson(QJsonDocument::Indented) << endl;
+    vout << "       Key: " + key << endl;
+    vout << "      Desc: " + summary << endl;
+    vout << "  Assignee: " + assignee << endl;
+    vout << "Components: " + components << endl;
+    vout << "  Reporter: " + reporter << endl;
+    vout << "Resolution: " + resolution << endl;
+    vout << "    Status: " + status << endl;
+    */
+
+    mainWindow->statusMessage(tr("Received Jira data.", "VymModel"));
+}
+
+
+void VymModel::setHeadingConfluencePageName()   // FIXME-2 always asks for Confluence credentials when adding any URL
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        QString url = selbi->getURL();
+        if (!url.isEmpty() && 
+                settings.contains("/atlassian/confluence/url") &&
+                url.contains(settings.value("/atlassian/confluence/url").toString())) {
+
+            ConfluenceAgent *ca_setHeading = new ConfluenceAgent(selbi);
+            ca_setHeading->setPageURL(url);
+            ca_setHeading->setJobType(ConfluenceAgent::CopyPagenameToHeading);
+            ca_setHeading->startJob();
+        }
+    }
+}
+
+void VymModel::setVymLink(const QString &s)
+{
+    if (s.isEmpty()) return;
+
+    BranchItem *bi = getSelectedBranch();
+    if (bi) {
+        saveState(
+            bi, "setVymLink (\"" + bi->getVymLink() + "\")", bi,
+            "setVymLink (\"" + s + "\")",
+            QString("Set vymlink of %1 to %2").arg(getObjectName(bi)).arg(s));
+        bi->setVymLink(s);
+        emitDataChanged(bi);
+        reposition();
+    }
+}
+
+void VymModel::deleteVymLink()
+{
+    BranchItem *bi = getSelectedBranch();
+    if (bi) {
+        saveState(bi, "setVymLink (\"" + bi->getVymLink() + "\")", bi,
+                  "setVymLink (\"\")",
+                  QString("Unset vymlink of %1").arg(getObjectName(bi)));
+        bi->setVymLink("");
+        emitDataChanged(bi);
+        reposition();
+        updateActions();
+    }
+}
+
+QString VymModel::getVymLink()
+{
+    BranchItem *bi = getSelectedBranch();
+    if (bi)
+        return bi->getVymLink();
+    else
+        return "";
+}
+
+QStringList VymModel::getVymLinks()
+{
+    QStringList links;
+    BranchItem *selbi = getSelectedBranch();
+    BranchItem *cur = NULL;
+    BranchItem *prev = NULL;
+    nextBranch(cur, prev, true, selbi);
+    while (cur) {
+        if (!cur->getVymLink().isEmpty())
+            links.append(cur->getVymLink());
+        nextBranch(cur, prev, true, selbi);
+    }
+    return links;
+}
+
+void VymModel::followXLink(int i)
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi) {
+        selbi = selbi->getXLinkItemNum(i)->getPartnerBranch();
+        if (selbi)
+            select(selbi);
+    }
+}
+
+void VymModel::editXLink()
+{
+    Link *l = getSelectedXLink();
+    if (l) {
+        EditXLinkDialog dia;
+        dia.setLink(l);
+        if (dia.exec() == QDialog::Accepted) {
+            if (dia.useSettingsGlobal()) {
+                setMapDefXLinkPen(l->getPen());
+                setMapDefXLinkStyleBegin(l->getStyleBeginString());
+                setMapDefXLinkStyleEnd(l->getStyleEndString());
+            }
+        }
+    }
+}
+
+void VymModel::setXLinkColor(const QString &new_col)
+{
+    Link *l = getSelectedXLink();
+    if (l) {
+        QPen pen = l->getPen();
+        QColor new_color = QColor(new_col);
+        QColor old_color = pen.color();
+        if (new_color == old_color)
+            return;
+        pen.setColor(new_color);
+        l->setPen(pen);
+        saveState(l->getBeginLinkItem(),
+                  QString("setXLinkColor(\"%1\")").arg(old_color.name()),
+                  l->getBeginLinkItem(),
+                  QString("setXLinkColor(\"%1\")").arg(new_color.name()),
+                  QString("set color of xlink to %1").arg(new_color.name()));
+    }
+}
+
+void VymModel::setXLinkStyle(const QString &new_style)
+{
+    Link *l = getSelectedXLink();
+    if (l) {
+        QPen pen = l->getPen();
+        QString old_style = penStyleToString(pen.style());
+        if (new_style == old_style)
+            return;
+        bool ok;
+        pen.setStyle(penStyle(new_style, ok));
+        l->setPen(pen);
+        saveState(l->getBeginLinkItem(),
+                  QString("setXLinkStyle(\"%1\")").arg(old_style),
+                  l->getBeginLinkItem(),
+                  QString("setXLinkStyle(\"%1\")").arg(new_style),
+                  QString("set style of xlink to %1").arg(new_style));
+    }
+}
+
+void VymModel::setXLinkStyleBegin(const QString &new_style)
+{
+    Link *l = getSelectedXLink();
+    if (l) {
+        QString old_style = l->getStyleBeginString();
+        if (new_style == old_style)
+            return;
+        l->setStyleBegin(new_style);
+        saveState(l->getBeginLinkItem(),
+                  QString("setXLinkStyleBegin(\"%1\")").arg(old_style),
+                  l->getBeginLinkItem(),
+                  QString("setXLinkStyleBegin(\"%1\")").arg(new_style),
+                  "set style of xlink begin");
+    }
+}
+
+void VymModel::setXLinkStyleEnd(const QString &new_style)
+{
+    Link *l = getSelectedXLink();
+    if (l) {
+        QString old_style = l->getStyleEndString();
+        if (new_style == old_style)
+            return;
+        l->setStyleEnd(new_style);
+        saveState(l->getBeginLinkItem(),
+                  QString("setXLinkStyleEnd(\"%1\")").arg(old_style),
+                  l->getBeginLinkItem(),
+                  QString("setXLinkStyleEnd(\"%1\")").arg(new_style),
+                  "set style of xlink end");
+    }
+}
+
+void VymModel::setXLinkWidth(int new_width)
+{
+    Link *l = getSelectedXLink();
+    if (l) {
+        QPen pen = l->getPen();
+        int old_width = pen.width();
+        if (new_width == old_width)
+            return;
+        pen.setWidth(new_width);
+        l->setPen(pen);
+        saveState(
+            l->getBeginLinkItem(), QString("setXLinkWidth(%1)").arg(old_width),
+            l->getBeginLinkItem(), QString("setXLinkWidth(%1)").arg(new_width),
+            "set width of xlink");
+    }
+}
+
+//////////////////////////////////////////////
+// Scripting
+//////////////////////////////////////////////
+
+QVariant VymModel::execute(
+    const QString &script) // FIXME-3 still required???
+                           // Called from these places:
+                           //
+                           // scripts/vym-ruby.rb  (and adaptormodel) used for
+                           // testing Main::callMacro Main::checkReleaseNotes
+                           // VymModel::undo
+                           // VymModel::redo
+                           // VymModel::exportLast
+                           // VymModel::updateSlideSelection
+{
+    // qDebug()<<"VM::execute called: "<<script;
+    return mainWindow->runScript(script);
+}
+
+void VymModel::setExportMode(bool b)
+{
+    // should be called before and after exports
+    // depending on the settings
+    if (b && settings.value("/export/useHideExport", "true") == "true")
+        setHideTmpMode(TreeItem::HideExport);
+    else
+        setHideTmpMode(TreeItem::HideNone);
+}
+
+QPointF VymModel::exportImage(QString fname, bool askName, QString format)
+{
+    QPointF offset; // set later, when getting image from MapEditor
+
+    if (fname == "") {
+        if (!askName) {
+            qWarning("VymModel::exportImage called without filename (and "
+                     "askName==false)");
+            return offset;
+        }
+
+        fname = lastImageDir.absolutePath() + "/" + getMapName() + ".png";
+        format = "PNG";
+    }
+
+    ExportBase ex;
+    ex.setName("Image");
+    ex.setModel(this);
+    ex.setFilePath(fname);
+    ex.setWindowTitle(tr("Export map as image"));
+    ex.addFilter(
+        "PNG (*.png);;All (* *.*)"); //  imageIO.getFilters().join(";;")
+    ex.setLastCommand(
+        settings.localValue(filePath, "/export/last/command", "").toString());
+
+    if (askName) {
+        if (!ex.execDialog())
+            return offset;
+        fname = ex.getFilePath();
+        lastImageDir = QDir(fname);
+    }
+
+    setExportMode(true);
+
+    mapEditor->minimizeView();
+
+    QImage img(mapEditor->getImage(offset));
+    if (!img.save(fname, format.toLocal8Bit())) {
+        QMessageBox::critical(
+            0, tr("Critical Error"),
+            tr("Couldn't save QImage %1 in format %2").arg(fname).arg(format));
+        ex.setResult(ExportBase::Failed);
+    } else
+        ex.setResult(ExportBase::Success);
+
+    setExportMode(false);
+
+    ex.completeExport();
+
+    return offset;
+}
+
+void VymModel::exportPDF(QString fname, bool askName)
+{
+    if (fname == "") {
+        if (!askName) {
+            qWarning("VymModel::exportPDF called without filename (and "
+                     "askName==false)");
+            return;
+        }
+
+        fname = lastExportDir.absolutePath() + "/" + getMapName() + ".pdf";
+    }
+
+    ExportBase ex;
+    ex.setName("PDF");
+    ex.setModel(this);
+    ex.setFilePath(fname);
+    ex.setWindowTitle(tr("Export map as PDF"));
+    ex.addFilter("PDF (*.pdf);;All (* *.*)");
+    ex.setLastCommand(
+        settings.localValue(filePath, "/export/last/command", "").toString());
+
+    if (askName) {
+        if (!ex.execDialog())
+            return;
+        fname = ex.getFilePath();
+    }
+
+    setExportMode(true);
+
+    // To PDF
+    QPrinter pdfPrinter(QPrinter::HighResolution);
+    pdfPrinter.setOutputFormat(QPrinter::PdfFormat);
+    pdfPrinter.setOutputFileName(fname);
+    pdfPrinter.setPageSize(QPageSize(QPageSize::A3));
+
+    QRectF bbox = mapEditor->getTotalBBox();
+    if (bbox.width() > bbox.height())
+        // recommend landscape
+        pdfPrinter.setPageOrientation(QPageLayout::Landscape);
+    else
+        // recommend portrait
+        pdfPrinter.setPageOrientation(QPageLayout::Portrait);
+
+    QPainter *pdfPainter = new QPainter(&pdfPrinter);
+    getScene()->render(pdfPainter);
+    pdfPainter->end();
+    delete pdfPainter;
+
+    setExportMode(false);
+
+    ex.completeExport();
+}
+
+QPointF VymModel::exportSVG(QString fname, bool askName)
+{
+    QPointF offset; // FIXME-3 not needed?
+
+    if (fname == "") {
+        if (!askName) {
+            qWarning("VymModel::exportSVG called without filename (and "
+                     "askName==false)");
+            return offset;
+        }
+
+        fname = lastImageDir.absolutePath() + "/" + getMapName() + ".svg";
+    }
+
+    ExportBase ex;
+    ex.setName("SVG");
+    ex.setModel(this);
+    ex.setFilePath(fname);
+    ex.setWindowTitle(tr("Export map as SVG"));
+    ex.addFilter("SVG (*.svg);;All (* *.*)");
+
+    if (askName) {
+        if (!ex.execDialog())
+            return offset;
+        fname = ex.getFilePath();
+        lastImageDir = QDir(fname);
+    }
+
+    setExportMode(true);
+
+    QSvgGenerator generator;
+    generator.setFileName(fname);
+    QSize sceneSize = getScene()->sceneRect().size().toSize();
+    generator.setSize(sceneSize);
+    generator.setViewBox(QRect(0, 0, sceneSize.width(), sceneSize.height()));
+    QPainter *svgPainter = new QPainter(&generator);
+    getScene()->render(svgPainter);
+    svgPainter->end();
+    delete svgPainter;
+
+    setExportMode(false);
+    ex.completeExport();
+
+    return offset;
+}
+
+void VymModel::exportXML(QString fpath, QString dpath, bool useDialog)
+{
+    ExportBase ex;
+    ex.setName("XML");
+    ex.setModel(this);
+    ex.setWindowTitle(tr("Export map as XML"));
+    ex.addFilter("XML (*.xml);;All (* *.*)");
+    ex.setLastCommand(
+        settings.localValue(filePath, "/export/last/command", "").toString());
+
+    if (useDialog) {
+        QFileDialog fd;
+        fd.setWindowTitle(vymName + " - " + tr("Export XML to directory"));
+        QStringList filters;
+        filters << "XML data (*.xml)";
+        fd.setNameFilters(filters);
+        fd.setOption(QFileDialog::DontConfirmOverwrite, true);
+        fd.setAcceptMode(QFileDialog::AcceptSave);
+        fd.selectFile(mapName + ".xml");
+
+        QString fn;
+        if (fd.exec() != QDialog::Accepted || fd.selectedFiles().isEmpty())
+            return;
+
+        fpath = fd.selectedFiles().first();
+        dpath = fpath.left(fpath.lastIndexOf("/"));
+
+        if (!confirmDirectoryOverwrite(QDir(dpath)))
+            return;
+    }
+    ex.setFilePath(fpath);
+
+    QString mname = basename(fpath);
+
+    // Hide stuff during export, if settings want this
+    setExportMode(true);
+
+    // Create subdirectories
+    makeSubDirs(dpath);
+
+    // write image and calculate offset (Remember old mapSaved setting while
+    // exporting image)
+    bool mchanged = mapChanged;
+    bool munsaved = mapUnsaved;
+
+    QPointF offset =
+        exportImage(dpath + "/images/" + mname + ".png", false, "PNG");
+
+    mapChanged = mchanged;
+    mapUnsaved = munsaved;
+
+    // write to directory   //FIXME-3 check totalBBox here...
+    QString saveFile =
+        saveToDir(dpath, mname + "-", FlagRowMaster::NoFlags, offset, NULL);
+    QFile file;
+
+    file.setFileName(fpath);
+    if (!file.open(QIODevice::WriteOnly)) {
+        // This should neverever happen
+        QMessageBox::critical(0, tr("Critical Export Error"),
+                              QString("VymModel::exportXML couldn't open %1")
+                                  .arg(file.fileName()));
+        return;
+    }
+
+    // Write it finally, and write in UTF8, no matter what
+    QTextStream ts(&file);
+    ts.setCodec("UTF-8");
+    ts << saveFile;
+    file.close();
+
+    setExportMode(false);
+
+    QStringList args;
+    args << fpath;
+    args << dpath;
+    ex.completeExport(args);
+}
+
+void VymModel::exportAO(QString fname, bool askName)
+{
+    ExportAO ex;
+    ex.setModel(this);
+    ex.setLastCommand(
+        settings.localValue(filePath, "/export/last/command", "").toString());
+
+    if (fname == "")
+        ex.setFilePath(mapName + ".txt");
+    else
+        ex.setFilePath(fname);
+
+    if (askName) {
+        ex.setDirPath(lastExportDir.absolutePath());
+        ex.execDialog();
+    }
+    if (!ex.canceled()) {
+        setExportMode(true);
+        ex.doExport();
+        setExportMode(false);
+    }
+}
+
+void VymModel::exportASCII(const QString &fname, bool listTasks, bool askName)
+{
+    ExportASCII ex;
+    ex.setModel(this);
+    ex.setListTasks(listTasks);
+    ex.setLastCommand(
+        settings.localValue(filePath, "/export/last/command", "").toString());
+
+    if (fname == "")
+        ex.setFilePath(mapName + ".txt");
+    else
+        ex.setFilePath(fname);
+
+    if (askName) {
+        ex.setDirPath(lastExportDir.absolutePath());
+        ex.execDialog();
+    }
+
+    if (!ex.canceled()) {
+        setExportMode(true);
+        ex.doExport();
+        setExportMode(false);
+    }
+}
+
+void VymModel::exportCSV(const QString &fname, bool askName)
+{
+    ExportCSV ex;
+    ex.setModel(this);
+    ex.setLastCommand(
+        settings.localValue(filePath, "/export/last/command", "").toString());
+
+    if (fname == "")
+        ex.setFilePath(mapName + ".csv");
+    else
+        ex.setFilePath(fname);
+
+    if (askName) {
+        ex.addFilter("CSV (*.csv);;All (* *.*)");
+        ex.setDirPath(lastExportDir.absolutePath());
+        ex.setWindowTitle(vymName + " -" + tr("Export as csv") + " " +
+                          tr("(still experimental)"));
+        ex.execDialog();
+    }
+
+    if (!ex.canceled()) {
+        setExportMode(true);
+        ex.doExport();
+        setExportMode(false);
+    }
+}
+
+void VymModel::exportFirefoxBookmarks(const QString &fname, bool askName)
+{
+    ExportFirefox ex;
+    ex.setModel(this);
+    ex.setLastCommand(
+        settings.localValue(filePath, "/export/last/command", "").toString());
+
+    if (fname == "")
+        ex.setFilePath(mapName + ".csv");
+    else
+        ex.setFilePath(fname);
+
+    if (askName) {
+        ex.addFilter("JSON (*.json);;All (* *.*)");
+        ex.setDirPath(lastExportDir.absolutePath());
+        ex.setWindowTitle(vymName + " -" + tr("Export as csv") + " " +
+                          tr("(still experimental)"));
+        ex.execDialog();
+    }
+
+    if (!ex.canceled()) {
+        setExportMode(true);
+        ex.doExport();
+        setExportMode(false);
+    }
+}
+
+void VymModel::exportHTML(const QString &fpath, const QString &dpath,
+                          bool useDialog)
+{
+    ExportHTML ex(this);
+    ex.setLastCommand(
+        settings.localValue(filePath, "/export/last/command", "").toString());
+
+    if (!dpath.isEmpty())
+        ex.setDirPath(dpath);
+    if (!fpath.isEmpty())
+        ex.setFilePath(fpath);
+
+    ex.doExport(useDialog);
+}
+
+void VymModel::exportConfluence(bool createPage, const QString &pageURL,
+                                const QString &pageName, bool useDialog)
+{
+    ExportConfluence ex(this);
+    ex.setCreateNewPage(createPage);
+    ex.setURL(pageURL);
+    ex.setPageName(pageName);
+    ex.setLastCommand(
+        settings.localValue(filePath, "/export/last/command", "").toString());
+
+    ex.doExport(useDialog);
+}
+
+void VymModel::exportImpress(const QString &fn, const QString &cf)
+{
+    ExportOO ex;
+    ex.setFilePath(fn);
+    ex.setModel(this);
+    ex.setLastCommand(
+        settings.localValue(filePath, "/export/last/command", "").toString());
+
+    if (ex.setConfigFile(cf)) {
+        QString lastCommand =
+            settings.localValue(filePath, "/export/last/command", "")
+                .toString();
+
+        setExportMode(true);
+        ex.exportPresentation();
+        setExportMode(false);
+
+        QString command =
+            settings.localValue(filePath, "/export/last/command", "")
+                .toString();
+        if (lastCommand != command)
+            setChanged();
+    }
+}
+
+bool VymModel::exportLastAvailable(QString &description, QString &command,
+                                   QString &dest)
+{
+    command =
+        settings.localValue(filePath, "/export/last/command", "").toString();
+
+    description = settings.localValue(filePath, "/export/last/description", "")
+                      .toString();
+    dest = settings.localValue(filePath, "/export/last/displayedDestination", "")
+               .toString();
+    if (!command.isEmpty() && command.contains("exportMap"))
+        return true;
+    else
+        return false;
+}
+
+void VymModel::exportLast()
+{
+    QString desc, command,
+        dest; // FIXME-3 better integrate configFile into command
+    if (exportLastAvailable(desc, command, dest)) {
+        //qDebug() << "VM::exportLast: " << command;
+        execute(command);
+    }
+}
+
+void VymModel::exportLaTeX(const QString &fname, bool askName)
+{
+    ExportLaTeX ex;
+    ex.setModel(this);
+    ex.setLastCommand(
+        settings.localValue(filePath, "/export/last/command", "").toString());
+
+    if (fname == "")
+        ex.setFilePath(mapName + ".tex");
+    else
+        ex.setFilePath(fname);
+
+    if (askName)
+        ex.execDialog();
+    if (!ex.canceled()) {
+        setExportMode(true);
+        ex.doExport();
+        setExportMode(false);
+    }
+}
+
+void VymModel::exportOrgMode(const QString &fname, bool askName)
+{
+    ExportOrgMode ex;
+    ex.setModel(this);
+    ex.setLastCommand(
+        settings.localValue(filePath, "/export/last/command", "").toString());
+
+    if (fname == "")
+        ex.setFilePath(mapName + ".org");
+    else
+        ex.setFilePath(fname);
+
+    if (askName) {
+        ex.setDirPath(lastExportDir.absolutePath());
+        ex.execDialog();
+    }
+
+    if (!ex.canceled()) {
+        setExportMode(true);
+        ex.doExport();
+        setExportMode(false);
+    }
+}
+
+void VymModel::exportMarkdown(const QString &fname, bool askName)
+{
+    ExportMarkdown ex;
+    ex.setModel(this);
+    ex.setLastCommand(
+        settings.localValue(filePath, "/export/last/command", "").toString());
+
+    if (fname == "")
+        ex.setFilePath(mapName + ".md");
+    else
+        ex.setFilePath(fname);
+
+    if (askName) {
+        ex.setDirPath(lastExportDir.absolutePath());
+        ex.execDialog();
+    }
+
+    if (!ex.canceled()) {
+        setExportMode(true);
+        ex.doExport();
+        setExportMode(false);
+    }
+}
+//////////////////////////////////////////////
+// View related
+//////////////////////////////////////////////
+
+void VymModel::registerMapEditor(QWidget *e) { mapEditor = (MapEditor *)e; }
+
+void VymModel::setMapZoomFactor(const double &d)
+{
+    zoomFactor = d;
+    mapEditor->setZoomFactorTarget(d);
+}
+
+void VymModel::setMapRotationAngle(const double &d)
+{
+    rotationAngle = d;
+    mapEditor->setAngleTarget(d);
+}
+
+void VymModel::setMapAnimDuration(const int &d) { animDuration = d; }
+
+void VymModel::setMapAnimCurve(const QEasingCurve &c) { animCurve = c; }
+
+bool VymModel::centerOnID(const QString &id)
+{
+    TreeItem *ti = findUuid(QUuid(id));
+    if (ti) {
+        LinkableMapObj *lmo = ((MapItem *)ti)->getLMO();
+        if (zoomFactor > 0 && lmo) {
+            mapEditor->setViewCenterTarget(lmo->getBBox().center(), zoomFactor,
+                                           rotationAngle, animDuration,
+                                           animCurve);
+            return true;
+        }
+    }
+    return false;
+}
+
+void VymModel::setContextPos(QPointF p)
+{
+    contextPos = p;
+    hasContextPos = true;
+}
+
+void VymModel::unsetContextPos()
+{
+    contextPos = QPointF();
+    hasContextPos = false;
+}
+
+void VymModel::reposition()
+{
+    if (repositionBlocked)
+        return;
+
+    BranchObj *bo;
+    for (int i = 0; i < rootItem->branchCount(); i++) {
+        bo = rootItem->getBranchObjNum(i);
+        if (bo)
+            bo->reposition(); //  for positioning heading
+        else
+            qDebug() << "VM::reposition bo=0";
+    }
+    mapEditor->getTotalBBox();
+
+    // required to *reposition* the selection box. size is already correct:
+    emitSelectionChanged(); //FIXME-2 better only update selection geometry
+}
+
+bool VymModel::setMapLinkStyle(const QString &s)
+{
+    QString snow;
+    switch (linkstyle) {
+    case LinkableMapObj::Line:
+        snow = "StyleLine";
+        break;
+    case LinkableMapObj::Parabel:
+        snow = "StyleParabel";
+        break;
+    case LinkableMapObj::PolyLine:
+        snow = "StylePolyLine";
+        break;
+    case LinkableMapObj::PolyParabel:
+        snow = "StylePolyParabel";
+        break;
+    default:
+        return false;
+        break;
+    }
+
+    saveState(QString("setMapLinkStyle (\"%1\")").arg(s),
+              QString("setMapLinkStyle (\"%1\")").arg(snow),
+              QString("Set map link style (\"%1\")").arg(s));
+
+    if (s == "StyleLine")
+        linkstyle = LinkableMapObj::Line;
+    else if (s == "StyleParabel")
+        linkstyle = LinkableMapObj::Parabel;
+    else if (s == "StylePolyLine")
+        linkstyle = LinkableMapObj::PolyLine;
+    else if (s == "StylePolyParabel")
+        linkstyle = LinkableMapObj::PolyParabel;
+    else
+        linkstyle = LinkableMapObj::UndefinedStyle;
+
+    BranchItem *cur = NULL;
+    BranchItem *prev = NULL;
+    BranchObj *bo;
+    nextBranch(cur, prev);
+    while (cur) {
+        bo = (BranchObj *)(cur->getLMO());
+        bo->setLinkStyle(bo->getDefLinkStyle(
+            cur->parent())); // FIXME-4 better emit dataCHanged and leave the
+                             // changes to View
+        nextBranch(cur, prev);
+    }
+    reposition();
+    return true;
+}
+
+LinkableMapObj::Style VymModel::getMapLinkStyle() { return linkstyle; }
+
+uint VymModel::getModelID() { return modelID; }
+
+void VymModel::setView(VymView *vv) { vymView = vv; }
+
+void VymModel::setMapDefLinkColor(QColor col)
+{
+    if (!col.isValid())
+        return;
+    saveState(
+        QString("setMapDefLinkColor (\"%1\")").arg(getMapDefLinkColor().name()),
+        QString("setMapDefLinkColor (\"%1\")").arg(col.name()),
+        QString("Set map link color to %1").arg(col.name()));
+
+    defLinkColor = col;
+
+    // Set color for "link arrows" in TreeEditor
+    vymView->setLinkColor(col);
+
+    BranchItem *cur = NULL;
+    BranchItem *prev = NULL;
+    BranchObj *bo;
+    nextBranch(cur, prev);
+    while (cur) {
+        bo = (BranchObj *)(cur->getLMO());
+        bo->setLinkColor();
+
+        for (int i = 0; i < cur->imageCount(); ++i)
+            cur->getImageNum(i)->getLMO()->setLinkColor();
+
+        nextBranch(cur, prev);
+    }
+    updateActions();
+}
+
+void VymModel::setMapLinkColorHintInt()
+{
+    // called from setMapLinkColorHint(lch) or at end of parse
+    BranchItem *cur = NULL;
+    BranchItem *prev = NULL;
+    BranchObj *bo;
+    nextBranch(cur, prev);
+    while (cur) {
+        bo = (BranchObj *)(cur->getLMO());
+        bo->setLinkColor();
+
+        for (int i = 0; i < cur->imageCount(); ++i)
+            cur->getImageNum(i)->getLMO()->setLinkColor();
+
+        nextBranch(cur, prev);
+    }
+}
+
+void VymModel::setMapLinkColorHint(LinkableMapObj::ColorHint lch)
+{
+    linkcolorhint = lch;
+    setMapLinkColorHintInt();
+}
+
+void VymModel::toggleMapLinkColorHint()
+{
+    if (linkcolorhint == LinkableMapObj::HeadingColor)
+        linkcolorhint = LinkableMapObj::DefaultColor;
+    else
+        linkcolorhint = LinkableMapObj::HeadingColor;
+    BranchItem *cur = NULL;
+    BranchItem *prev = NULL;
+    BranchObj *bo;
+    nextBranch(cur, prev);
+    while (cur) {
+        bo = (BranchObj *)(cur->getLMO());
+        bo->setLinkColor();
+
+        for (int i = 0; i < cur->imageCount(); ++i)
+            cur->getImageNum(i)->getLMO()->setLinkColor();
+
+        nextBranch(cur, prev);
+    }
+}
+
+void VymModel::
+    selectMapBackgroundImage() // FIXME-3 for using background image:
+                               // view.setCacheMode(QGraphicsView::CacheBackground);
+                               // Also this belongs into ME
+{
+    QStringList filters;
+    filters << tr("Images") +
+                   " (*.png *.bmp *.xbm *.jpg *.png *.xpm *.gif *.pnm)";
+    QFileDialog fd;
+    fd.setFileMode(QFileDialog::ExistingFile);
+    fd.setWindowTitle(vymName + " - " + tr("Load background image"));
+    fd.setDirectory(lastImageDir);
+    fd.setAcceptMode(QFileDialog::AcceptOpen);
+
+    if (fd.exec() == QDialog::Accepted && !fd.selectedFiles().isEmpty()) {
+        // TODO selectMapBackgroundImg in QT4 use:  lastImageDir=fd.directory();
+        lastImageDir = QDir(fd.directory().path());
+        setMapBackgroundImage(fd.selectedFiles().first());
+    }
+}
+
+void VymModel::setMapBackgroundImage(
+    const QString &fn) // FIXME-3 missing savestate, move to ME
+{
+    /*
+    QColor oldcol=mapEditor->getScene()->backgroundBrush().color();
+    saveState(
+    selection,
+    QString ("setMapBackgroundImage (%1)").arg(oldcol.name()),
+    selection,
+    QString ("setMapBackgroundImage (%1)").arg(col.name()),
+    QString("Set background color of map to %1").arg(col.name()));
+    */
+    QBrush brush;
+    brush.setTextureImage(QImage(fn));
+    mapEditor->getScene()->setBackgroundBrush(brush);
+}
+
+void VymModel::selectMapBackgroundColor()
+{
+    QColor col = QColorDialog::getColor(
+        mapEditor->getScene()->backgroundBrush().color(), NULL);
+    if (!col.isValid())
+        return;
+    setMapBackgroundColor(col);
+}
+
+void VymModel::setMapBackgroundColor(QColor col)
+{
+    QColor oldcol = mapEditor->getScene()->backgroundBrush().color();
+    saveState(QString("setMapBackgroundColor (\"%1\")").arg(oldcol.name()),
+              QString("setMapBackgroundColor (\"%1\")").arg(col.name()),
+              QString("Set background color of map to %1").arg(col.name()));
+    backgroundColor = col;  // Used for backroundRole in TreeModel::data()
+    vymView->setBackgroundColor(backgroundColor);
+}
+
+QColor VymModel::getMapBackgroundColor() // FIXME-4 move to ME
+{
+    return mapEditor->getScene()->backgroundBrush().color();
+}
+
+QFont VymModel::getMapDefaultFont() { return defaultFont; }
+
+void VymModel::setMapDefaultFont(const QFont &f) { defaultFont = f; }
+
+LinkableMapObj::ColorHint VymModel::getMapLinkColorHint() // FIXME-4 move to ME
+{
+    return linkcolorhint;
+}
+
+QColor VymModel::getMapDefLinkColor() // FIXME-4 move to ME
+{
+    return defLinkColor;
+}
+
+void VymModel::setMapDefXLinkPen(const QPen &p) // FIXME-4 move to ME
+{
+    defXLinkPen = p;
+}
+
+QPen VymModel::getMapDefXLinkPen() // FIXME-4 move to ME
+{
+    return defXLinkPen;
+}
+
+void VymModel::setMapDefXLinkStyleBegin(const QString &s)
+{
+    defXLinkStyleBegin = s;
+}
+
+QString VymModel::getMapDefXLinkStyleBegin() { return defXLinkStyleBegin; }
+
+void VymModel::setMapDefXLinkStyleEnd(const QString &s)
+{
+    defXLinkStyleEnd = s;
+}
+
+QString VymModel::getMapDefXLinkStyleEnd() { return defXLinkStyleEnd; }
+
+void VymModel::move(const double &x, const double &y)
+{
+    MapItem *seli = (MapItem *)getSelectedItem();
+    if (seli &&
+        (seli->isBranchLikeType() || seli->getType() == TreeItem::Image)) {
+        LinkableMapObj *lmo = seli->getLMO();
+        if (lmo) {
+            QPointF ap(lmo->getAbsPos());
+            QPointF to(x, y);
+            if (ap != to) {
+                QString ps = qpointFToString(ap);
+                QString s = getSelectString(seli);
+                saveState(
+                    s, "move " + ps, s, "move " + qpointFToString(to),
+                    QString("Move %1 to %2").arg(getObjectName(seli)).arg(ps));
+                lmo->move(x, y);
+                reposition();
+                emitSelectionChanged();
+            }
+        }
+    }
+}
+
+void VymModel::moveRel(const double &x, const double &y)
+{
+    MapItem *seli = (MapItem *)getSelectedItem();
+    if (seli &&
+        (seli->isBranchLikeType() || seli->getType() == TreeItem::Image)) {
+        LinkableMapObj *lmo = seli->getLMO();
+        if (lmo) {
+            QPointF rp(lmo->getRelPos());
+            QPointF to(x, y);
+            if (rp != to) {
+                QString ps = qpointFToString(lmo->getRelPos());
+                QString s = getSelectString(seli);
+                saveState(s, "moveRel " + ps, s,
+                          "moveRel " + qpointFToString(to),
+                          QString("Move %1 to relative position %2")
+                              .arg(getObjectName(seli))
+                              .arg(ps));
+                ((OrnamentedObj *)lmo)->move2RelPos(x, y);
+                reposition();
+                lmo->updateLinkGeometry();
+                emitSelectionChanged();
+            }
+        }
+    }
+}
+
+void VymModel::animate()
+{
+    animationTimer->stop();
+    BranchObj *bo;
+    int i = 0;
+    while (i < animObjList.size()) {
+        bo = (BranchObj *)animObjList.at(i);
+        if (!bo->animate()) {
+            if (i >= 0) {
+                animObjList.removeAt(i);
+                i--;
+            }
+        }
+        bo->reposition();
+        i++;
+    }
+    emitSelectionChanged();
+
+    if (!animObjList.isEmpty())
+        animationTimer->start(animationInterval);
+}
+
+void VymModel::startAnimation(BranchObj *bo, const QPointF &v)
+{
+    if (!bo)
+        return;
+
+    if (bo->getUseRelPos())
+        startAnimation(bo, bo->getRelPos(), bo->getRelPos() + v);
+    else
+        startAnimation(bo, bo->getAbsPos(), bo->getAbsPos() + v);
+}
+
+void VymModel::startAnimation(BranchObj *bo, const QPointF &start,
+                              const QPointF &dest)
+{
+    if (start == dest)
+        return;
+    if (bo && bo->getTreeItem()->depth() >= 0) {
+        AnimPoint ap;
+        ap.setStart(start);
+        ap.setDest(dest);
+        ap.setTicks(animationTicks);
+        ap.setAnimated(true);
+        bo->setAnimation(ap);
+        if (!animObjList.contains(bo))
+            animObjList.append(bo);
+        animationTimer->setSingleShot(true);
+        animationTimer->start(animationInterval);
+    }
+}
+
+void VymModel::stopAnimation(MapObj *mo)
+{
+    int i = animObjList.indexOf(mo);
+    if (i >= 0)
+        animObjList.removeAt(i);
+}
+
+void VymModel::stopAllAnimation()
+{
+    BranchObj *bo;
+    int i = 0;
+    while (i < animObjList.size()) {
+        bo = (BranchObj *)animObjList.at(i);
+        bo->stopAnimation();
+        bo->requestReposition();
+        i++;
+    }
+    reposition();
+}
+
+void VymModel::sendSelection()
+{
+    if (netstate != Server)
+        return;
+    sendData(QString("select (\"%1\")").arg(getSelectString()));
+}
+
+void VymModel::newServer()
+{
+    port = 54321;
+    sendCounter = 0;
+    tcpServer = new QTcpServer(this);
+    if (!tcpServer->listen(QHostAddress::Any, port)) {
+        QMessageBox::critical(NULL, "vym server",
+                              QString("Unable to start the server: %1.")
+                                  .arg(tcpServer->errorString()));
+        // FIXME-3 needed? we are no widget any longer... close();
+        return;
+    }
+    connect(tcpServer, SIGNAL(newConnection()), this, SLOT(newClient()));
+    netstate = Server;
+    qDebug() << "Server is running on port " << tcpServer->serverPort();
+}
+
+void VymModel::connectToServer()
+{
+    port = 54321;
+    server = "salam.suse.de";
+    server = "localhost";
+    clientSocket = new QTcpSocket(this);
+    clientSocket->abort();
+    clientSocket->connectToHost(server, port);
+    connect(clientSocket, SIGNAL(readyRead()), this, SLOT(readData()));
+    connect(clientSocket, SIGNAL(error(QAbstractSocket::SocketError)), this,
+            SLOT(displayNetworkError(QAbstractSocket::SocketError)));
+    netstate = Client;
+    qDebug() << "connected to " << qPrintable(server) << " port " << port;
+}
+
+void VymModel::newClient()
+{
+    QTcpSocket *newClient = tcpServer->nextPendingConnection();
+    connect(newClient, SIGNAL(disconnected()), newClient, SLOT(deleteLater()));
+
+    qDebug() << "ME::newClient  at "
+             << qPrintable(newClient->peerAddress().toString());
+
+    clientList.append(newClient);
+}
+
+void VymModel::sendData(const QString &s)
+{
+    if (clientList.size() == 0)
+        return;
+
+    // Create bytearray to send
+    QByteArray block;
+    QDataStream out(&block, QIODevice::WriteOnly);
+    out.setVersion(QDataStream::Qt_4_0);
+
+    // Reserve some space for blocksize
+    out << (quint16)0;
+
+    // Write sendCounter
+    out << sendCounter++;
+
+    // Write data
+    out << s;
+
+    // Go back and write blocksize so far
+    out.device()->seek(0);
+    quint16 bs = (quint16)(block.size() - 2 * sizeof(quint16));
+    out << bs;
+
+    if (debug)
+        qDebug() << "ME::sendData  bs=" << bs << "  counter=" << sendCounter
+                 << "  s=" << qPrintable(s);
+
+    for (int i = 0; i < clientList.size(); ++i) {
+        // qDebug() << "Sending \""<<qPrintable (s)<<"\" to "<<qPrintable
+        // (clientList.at(i)->peerAddress().toString());
+        clientList.at(i)->write(block);
+    }
+}
+
+void VymModel::readData()
+{
+    while (clientSocket->bytesAvailable() >= (int)sizeof(quint16)) {
+        if (debug)
+            qDebug() << "readData  bytesAvail="
+                     << clientSocket->bytesAvailable();
+        quint16 recCounter;
+        quint16 blockSize;
+
+        QDataStream in(clientSocket);
+        in.setVersion(QDataStream::Qt_4_0);
+
+        in >> blockSize;
+        in >> recCounter;
+
+        QString t;
+        in >> t;
+        if (debug)
+            qDebug() << "VymModel::readData  command=" << qPrintable(t);
+        // bool noErr;
+        // QString errMsg;
+        // parseAtom (t,noErr,errMsg);    //FIXME-4 needs rework using scripts
+    }
+    return;
+}
+
+void VymModel::displayNetworkError(QAbstractSocket::SocketError socketError)
+{
+    switch (socketError) {
+    case QAbstractSocket::RemoteHostClosedError:
+        break;
+    case QAbstractSocket::HostNotFoundError:
+        QMessageBox::information(NULL, vymName + " Network client",
+                                 "The host was not found. Please check the "
+                                 "host name and port settings.");
+        break;
+    case QAbstractSocket::ConnectionRefusedError:
+        QMessageBox::information(NULL, vymName + " Network client",
+                                 "The connection was refused by the peer. "
+                                 "Make sure the fortune server is running, "
+                                 "and check that the host name and port "
+                                 "settings are correct.");
+        break;
+    default:
+        QMessageBox::information(NULL, vymName + " Network client",
+                                 QString("The following error occurred: %1.")
+                                     .arg(clientSocket->errorString()));
+    }
+}
+
+void VymModel::downloadImage(const QUrl &url, BranchItem *bi)
+{
+    if (!bi)
+        bi = getSelectedBranch();
+    if (!bi) {
+        qWarning("VM::download bi==NULL");
+        return;
+    }
+
+    // FIXME-3 download img to tmpfile and delete after running script in
+    // mainWindow
+    QString script;
+    script += QString("m = vym.currentMap();m.selectID(\"%1\");")
+                  .arg(bi->getUuid().toString());
+    script += QString("m.loadImage(\"$TMPFILE\");");
+
+    DownloadAgent *agent = new DownloadAgent(url);
+    agent->setFinishedAction(this, script);
+    connect(agent, SIGNAL(downloadFinished()), mainWindow,
+            SLOT(downloadFinished()));
+    QTimer::singleShot(0, agent, SLOT(execute()));
+}
+
+void VymModel::selectMapSelectionColor()    // FIXME-2 move out of VymModel, consider Pen/Brush
+{
+    QColor col = QColorDialog::getColor(defLinkColor, NULL);
+    setSelectionPenColor(col);
+    setSelectionBrushColor(col);
+}
+
+void VymModel::emitSelectionChanged(const QItemSelection &newsel)
+{
+    emit(selectionChanged(newsel,
+                          newsel)); // needed e.g. to update geometry in editor
+    sendSelection();
+}
+
+void VymModel::emitSelectionChanged()
+{
+    QItemSelection newsel = selModel->selection();
+    emitSelectionChanged(newsel);
+}
+
+void VymModel::setSelectionPenColor(QColor col)
+{
+    if (!col.isValid())
+        return;
+
+    QPen selPen = mapEditor->getSelectionPen();
+    saveState(QString("setSelectionPenColor (\"%1\")")
+                  .arg(selPen.color().name()),
+              QString("setSelectionPenColor (\"%1\")").arg(col.name()),
+              QString("Set pen color of selection box to %1").arg(col.name()));
+
+    selPen.setColor(col);
+    mapEditor->setSelectionPen(selPen);
+}
+
+QColor VymModel::getSelectionPenColor() {
+    return mapEditor->getSelectionPen().color();
+}
+
+void VymModel::setSelectionPenWidth(qreal w)
+{
+    QPen selPen = mapEditor->getSelectionPen();
+    
+    saveState(QString("setSelectionPenWidth (\"%1\")")
+                  .arg(mapEditor->getSelectionPen().width()),
+              QString("setSelectionPenWidth (\"%1\")").arg(w),
+              QString("Set pen width of selection box to %1").arg(w));
+
+    selPen.setWidth(w);
+    mapEditor->setSelectionPen(selPen);
+    //vymView->setSelectionColor(col);
+}
+
+qreal VymModel::getSelectionPenWidth() {
+    return mapEditor->getSelectionPen().width();
+}
+
+void VymModel::setSelectionBrushColor(QColor col)
+{
+    if (!col.isValid())
+        return;
+
+    QBrush selBrush = mapEditor->getSelectionBrush();
+    saveState(QString("setSelectionBrushColor (\"%1\")")
+                  .arg(selBrush.color().name()),
+              QString("setSelectionBrushColor (\"%1\")").arg(col.name()),
+              QString("Set Brush color of selection box to %1").arg(col.name()));
+
+    selBrush.setColor(col);
+    vymView->setSelectionBrush(selBrush);
+}
+
+QColor VymModel::getSelectionBrushColor() {
+    return mapEditor->getSelectionBrush().color();
+}
+
+bool VymModel::initIterator(const QString &iname, bool deepLevelsFirst)
+{
+    Q_UNUSED(deepLevelsFirst);
+
+    // Remove existing iterators first
+    selIterCur.remove(iname);
+    selIterPrev.remove(iname);
+    selIterStart.remove(iname);
+    selIterActive.remove(iname);
+
+    QList<BranchItem *> selbis;
+    selbis = getSelectedBranches();
+    if (selbis.count() == 1) {
+        BranchItem *prev = NULL;
+        BranchItem *cur = NULL;
+        nextBranch(cur, prev, false, selbis.first());
+        if (cur) {
+            selIterCur.insert(iname, cur->getUuid());
+            selIterPrev.insert(iname, prev->getUuid());
+            selIterStart.insert(iname, selbis.first()->getUuid());
+            selIterActive.insert(iname, false);
+            // qDebug() << "Created iterator " << iname;
+            return true;
+        }
+    }
+    return false;
+}
+
+bool VymModel::nextIterator(const QString &iname)
+{
+    if (selIterCur.keys().indexOf(iname) < 0) {
+        qWarning()
+            << QString("VM::nextIterator couldn't find %1 in hash of iterators")
+                   .arg(iname);
+        return false;
+    }
+
+    BranchItem *cur = (BranchItem *)(findUuid(selIterCur.value(iname)));
+    if (!cur) {
+        qWarning() << "VM::nextIterator couldn't find cur" << selIterCur;
+        return false;
+    }
+
+    qDebug() << "  " << iname << "selecting " << cur->getHeadingPlain();
+    select(cur);
+
+    if (!selIterActive.value(iname)) {
+        // Select for the first time
+        select(cur);
+        selIterActive[iname] = true;
+        return true;
+    }
+
+    BranchItem *prev = (BranchItem *)(findUuid(selIterPrev.value(iname)));
+    BranchItem *start = (BranchItem *)(findUuid(selIterStart.value(iname)));
+    if (!prev)
+        qWarning() << "VM::nextIterator couldn't find prev"
+                   << selIterPrev.value(iname);
+    if (!start)
+        qWarning() << "VM::nextIterator couldn't find start "
+                   << selIterStart.value(iname);
+
+    if (cur && prev && start) {
+        nextBranch(cur, prev, false, start);
+        if (cur) {
+            selIterCur[iname] = cur->getUuid();
+            selIterPrev[iname] = prev->getUuid();
+            select(cur);
+            return true;
+        }
+        else
+            return false;
+    }
+    return false;
+}
+
+void VymModel::setHideTmpMode(TreeItem::HideTmpMode mode)
+{
+    hidemode = mode;
+    for (int i = 0; i < rootItem->branchCount(); i++)
+        rootItem->getBranchNum(i)->setHideTmp(mode);
+    reposition();
+    if (mode == TreeItem::HideExport)
+        unselectAll();
+    else
+        reselect();
+
+    qApp->processEvents();
+}
+
+//////////////////////////////////////////////
+// Selection related
+//////////////////////////////////////////////
+
+void VymModel::updateSelection(QItemSelection newsel, QItemSelection dsel)
+{
+    QModelIndex ix;
+    MapItem *mi;
+    BranchItem *bi;
+    bool do_reposition = false;
+    foreach (ix, dsel.indexes()) {
+        mi = static_cast<MapItem *>(ix.internalPointer());
+        if (mi->isBranchLikeType())
+            do_reposition =
+                do_reposition || ((BranchItem *)mi)->resetTmpUnscroll();
+        if (mi->getType() == TreeItem::XLink) {
+            Link *li = ((XLinkItem *)mi)->getLink();
+            XLinkObj *xlo = li->getXLinkObj();
+            if (xlo)
+                xlo->setSelection(XLinkObj::Unselected);
+
+            do_reposition =
+                do_reposition || li->getBeginBranch()->resetTmpUnscroll();
+            do_reposition =
+                do_reposition || li->getEndBranch()->resetTmpUnscroll();
+        }
+    }
+
+    foreach (ix, newsel.indexes()) {
+        mi = static_cast<MapItem *>(ix.internalPointer());
+        if (mi->isBranchLikeType()) {
+            bi = (BranchItem *)mi;
+            if (bi->hasScrolledParent()) {
+                bi->tmpUnscroll();
+                do_reposition = true;
+            }
+        }
+        if (mi->getType() == TreeItem::XLink) {
+            ((XLinkItem *)mi)->setSelection();
+
+            // begin/end branches need to be tmp unscrolled
+            Link *li = ((XLinkItem *)mi)->getLink();
+            bi = li->getBeginBranch();
+            if (bi->hasScrolledParent()) {
+                bi->tmpUnscroll();
+                do_reposition = true;
+            }
+            bi = li->getEndBranch();
+            if (bi->hasScrolledParent()) {
+                bi->tmpUnscroll();
+                do_reposition = true;
+            }
+        }
+    }
+    if (do_reposition)
+        reposition();
+}
+
+void VymModel::setSelectionModel(QItemSelectionModel *sm) { selModel = sm; }
+
+QItemSelectionModel *VymModel::getSelectionModel() { return selModel; }
+
+void VymModel::setSelectionBlocked(bool b) { selectionBlocked = b; }
+
+bool VymModel::isSelectionBlocked() { return selectionBlocked; }
+
+bool VymModel::select(const QString &s) // FIXME-2 Does not support multiple selections yet
+{
+    if (s.isEmpty())
+        return false;
+    TreeItem *ti = findBySelectString(s);
+    if (ti)
+        return select(index(ti));
+    return false;
+}
+
+bool VymModel::selectID(const QString &s)
+{
+    if (s.isEmpty())
+        return false;
+    TreeItem *ti = findUuid(QUuid(s));
+    if (ti)
+        return select(index(ti));
+    return false;
+}
+
+bool VymModel::select(LinkableMapObj *lmo)
+{
+    QItemSelection oldsel = selModel->selection();
+
+    if (lmo)
+        return select(lmo->getTreeItem());
+    else
+        return false;
+}
+
+bool VymModel::selectToggle(TreeItem *ti)
+{
+    if (ti) {
+        selModel->select(index(ti), QItemSelectionModel::Toggle);
+        // appendSelectionToHistory(); // FIXME-4 selection history not implemented yet
+        // for multiselections
+        lastToggledUuid = ti->getUuid();
+        return true;
+    }
+    return false;
+}
+
+bool VymModel::selectToggle(const QString &selectString)
+{
+    TreeItem *ti = findBySelectString(selectString);
+    return selectToggle(ti);
+}
+
+bool VymModel::select(TreeItem *ti)
+{
+    if (ti)
+        return select(index(ti));
+    else
+        return false;
+}
+
+bool VymModel::select(const QModelIndex &index)
+{
+    if (index.isValid()) {
+        TreeItem *ti = getItem(index);
+        if (ti->isBranchLikeType()) {
+            if (((BranchItem *)ti)->tmpUnscroll())
+                reposition();
+        }
+        selModel->select(index, QItemSelectionModel::ClearAndSelect);
+        appendSelectionToHistory();
+        return true;
+    }
+    return false;
+}
+
+void VymModel::unselectAll() { unselect(selModel->selection()); }
+
+void VymModel::unselect(QItemSelection desel)
+{
+    if (!desel.isEmpty()) {
+        lastSelectString = getSelectString();
+        selModel->clearSelection();
+    }
+}
+
+bool VymModel::reselect()
+{
+    bool b = select(lastSelectString);
+    return b;
+}
+
+bool VymModel::canSelectPrevious()
+{
+    if (currentSelection > 0)
+        return true;
+    else
+        return false;
+}
+
+bool VymModel::selectPrevious()
+{
+    keepSelectionHistory = true;
+    bool result = false;
+    while (currentSelection > 0) {
+        currentSelection--;
+        TreeItem *ti = findID(selectionHistory.at(currentSelection));
+        if (ti) {
+            result = select(ti);
+            break;
+        }
+        else
+            selectionHistory.removeAt(currentSelection);
+    }
+    keepSelectionHistory = false;
+    return result;
+}
+
+bool VymModel::canSelectNext()
+{
+    if (currentSelection < selectionHistory.count() - 1)
+        return true;
+    else
+        return false;
+}
+
+bool VymModel::selectNext()
+{
+    keepSelectionHistory = true;
+    bool result = false;
+    while (currentSelection < selectionHistory.count() - 1) {
+        currentSelection++;
+        TreeItem *ti = findID(selectionHistory.at(currentSelection));
+        if (ti) {
+            result = select(ti);
+            break;
+        }
+        else
+            selectionHistory.removeAt(currentSelection);
+    }
+    keepSelectionHistory = false;
+    return result;
+}
+
+void VymModel::resetSelectionHistory()
+{
+    selectionHistory.clear();
+    currentSelection = -1;
+    keepSelectionHistory = false;
+    appendSelectionToHistory();
+}
+
+void VymModel::appendSelectionToHistory() // FIXME-4 history unable to cope with multiple
+                                          // selections
+{
+    uint id = 0;
+    TreeItem *ti = getSelectedItem();
+    if (ti && !keepSelectionHistory) {
+        if (ti->isBranchLikeType())
+            ((BranchItem *)ti)->setLastSelectedBranch();
+        id = ti->getID();
+        selectionHistory.append(id);
+        currentSelection = selectionHistory.count() - 1;
+        updateActions();
+    }
+}
+
+void VymModel::emitShowSelection(bool scaled)
+{
+    if (!repositionBlocked)
+        emit(showSelection(scaled));
+}
+
+TreeItem* VymModel::lastToggledItem()
+{
+    return findUuid(lastToggledUuid);
+}
+
+void VymModel::emitNoteChanged(TreeItem *ti)
+{
+    QModelIndex ix = index(ti);
+    emit(noteChanged(ix));
+    mainWindow->updateNoteEditor(ti);
+}
+
+void VymModel::emitDataChanged(TreeItem *ti)
+{
+    QModelIndex ix = index(ti);
+    emit(dataChanged(ix, ix));
+    emitSelectionChanged();
+    if (!repositionBlocked) {
+        // Update taskmodel and recalc priorities there
+        if (ti->isBranchLikeType() && ((BranchItem *)ti)->getTask()) {
+            taskModel->emitDataChanged(((BranchItem *)ti)->getTask());
+            taskModel->recalcPriorities();
+        }
+    }
+}
+
+void VymModel::emitUpdateQueries()
+{
+    // Used to tell MainWindow to update query results
+    if (repositionBlocked)
+        return;
+    emit(updateQueries(this));
+}
+void VymModel::emitUpdateLayout()
+{
+    if (settings.value("/mainwindow/autoLayout/use", "true") == "true")
+        emit(updateLayout());
+}
+
+bool VymModel::selectFirstBranch()
+{
+    TreeItem *ti = getSelectedBranch();
+    if (ti) {
+        TreeItem *par = ti->parent();
+        if (par) {
+            TreeItem *ti2 = par->getFirstBranch();
+            if (ti2)
+                return select(ti2);
+        }
+    }
+    return false;
+}
+
+bool VymModel::selectFirstChildBranch()
+{
+    TreeItem *ti = getSelectedBranch();
+    if (ti) {
+        BranchItem *bi = ti->getFirstBranch();
+        if (bi)
+            return select(bi);
+    }
+    return false;
+}
+
+bool VymModel::selectLastBranch()
+{
+    TreeItem *ti = getSelectedBranch();
+    if (ti) {
+        TreeItem *par = ti->parent();
+        if (par) {
+            TreeItem *ti2 = par->getLastBranch();
+            if (ti2)
+                return select(ti2);
+        }
+    }
+    return false;
+}
+
+bool VymModel::selectLastChildBranch()
+{
+    TreeItem *ti = getSelectedBranch();
+    if (ti) {
+        BranchItem *bi = ti->getLastBranch();
+        if (bi)
+            return select(bi);
+    }
+    return false;
+}
+
+bool VymModel::selectLastSelectedBranch()
+{
+    BranchItem *bi = getSelectedBranch();
+    if (bi) {
+        bi = bi->getLastSelectedBranch();
+        if (bi)
+            return select(bi);
+    }
+    return false;
+}
+
+bool VymModel::selectLastImage()
+{
+    TreeItem *ti = getSelectedBranch();
+    if (ti) {
+        TreeItem *par = ti->parent();
+        if (par) {
+            TreeItem *ti2 = par->getLastImage();
+            if (ti2)
+                return select(ti2);
+        }
+    }
+    return false;
+}
+
+bool VymModel::selectLatestAdded() { return select(latestAddedItem); }
+
+bool VymModel::selectParent()
+{
+    TreeItem *ti = getSelectedItem();
+    TreeItem *par;
+    if (ti) {
+        par = ti->parent();
+        if (par)
+            return select(par);
+    }
+    return false;
+}
+
+TreeItem::Type VymModel::selectionType()
+{
+    TreeItem *ti = getSelectedItem();
+    if (ti)
+        return ti->getType();
+    else
+        return TreeItem::Undefined;
+}
+
+LinkableMapObj *VymModel::getSelectedLMO()
+{
+    QModelIndexList list = selModel->selectedIndexes();
+    if (list.count() == 1) {
+        TreeItem *ti = getItem(list.first());
+        TreeItem::Type type = ti->getType();
+        if (type == TreeItem::Branch || type == TreeItem::MapCenter ||
+            type == TreeItem::Image)
+            return ((MapItem *)ti)->getLMO();
+    }
+    return NULL;
+}
+
+BranchObj *VymModel::getSelectedBranchObj() // convenience function
+{
+    TreeItem *ti = getSelectedBranch();
+    if (ti)
+        return (BranchObj *)(((MapItem *)ti)->getLMO());
+    else
+        return NULL;
+}
+
+BranchItem *VymModel::getSelectedBranch()
+{
+    TreeItem *ti = getSelectedItem();
+    if (ti) {
+        TreeItem::Type type = ti->getType();
+        if (type == TreeItem::Branch || type == TreeItem::MapCenter)
+            return (BranchItem *)ti;
+    }
+    return NULL;
+}
+
+QList<BranchItem *> VymModel::getSelectedBranches()
+{
+    QList<BranchItem *> bis;
+    foreach (TreeItem *ti, getSelectedItems()) {
+        TreeItem::Type type = ti->getType();
+        if (type == TreeItem::Branch || type == TreeItem::MapCenter)
+            bis.append((BranchItem *)ti);
+    }
+    return bis;
+}
+
+ImageItem *VymModel::getSelectedImage()
+{
+    TreeItem *ti = getSelectedItem();
+    if (ti && ti->getType() == TreeItem::Image)
+        return (ImageItem *)ti;
+    else
+        return NULL;
+}
+
+Task *VymModel::getSelectedTask()
+{
+    BranchItem *selbi = getSelectedBranch();
+    if (selbi)
+        return selbi->getTask();
+    else
+        return NULL;
+}
+
+Link *VymModel::getSelectedXLink()
+{
+    XLinkItem *xli = getSelectedXLinkItem();
+    if (xli)
+        return xli->getLink();
+    return NULL;
+}
+
+XLinkItem *VymModel::getSelectedXLinkItem()
+{
+    TreeItem *ti = getSelectedItem();
+    if (ti && ti->getType() == TreeItem::XLink)
+        return (XLinkItem *)ti;
+    else
+        return NULL;
+}
+
+AttributeItem *VymModel::getSelectedAttribute()
+{
+    TreeItem *ti = getSelectedItem();
+    if (ti && ti->getType() == TreeItem::Attribute)
+        return (AttributeItem *)ti;
+    else
+        return NULL;
+}
+
+TreeItem *VymModel::getSelectedItem()
+{
+    if (!selModel)
+        return NULL;
+    QModelIndexList list = selModel->selectedIndexes();
+    if (list.count() == 1)
+        return getItem(list.first());
+    else
+        return NULL;
+}
+
+QList<TreeItem *> VymModel::getSelectedItems()
+{
+    QList<TreeItem *> l;
+    if (!selModel)
+        return l;
+    QModelIndexList list = selModel->selectedIndexes();
+    foreach (QModelIndex ix, list)
+        l.append(getItem(ix));
+    return l;
+}
+
+QModelIndex VymModel::getSelectedIndex()
+{
+    QModelIndexList list = selModel->selectedIndexes();
+    if (list.count() == 1)
+        return list.first();
+    else
+        return QModelIndex();
+}
+
+QList<uint> VymModel::getSelectedIDs()
+{
+    QList<uint> uids;
+    foreach (TreeItem *ti, getSelectedItems())
+        uids.append(ti->getID());
+    return uids;
+}
+
+QStringList VymModel::getSelectedUUIDs()
+{
+    QStringList uids;
+    foreach (TreeItem *ti, getSelectedItems())
+        uids.append(ti->getUuid().toString());
+    return uids;
+}
+
+bool VymModel::isSelected(TreeItem *ti)
+{
+    return getSelectedItems().contains(ti);
+}
+
+QString VymModel::getSelectString()
+{
+    return getSelectString(getSelectedItem());
+}
+
+QString VymModel::getSelectString(
+    LinkableMapObj *lmo) // only for convenience. Used in MapEditor
+{
+    if (!lmo)
+        return QString();
+    return getSelectString(lmo->getTreeItem());
+}
+
+QString VymModel::getSelectString(TreeItem *ti)
+{
+    QString s;
+    if (!ti || ti->depth() < 0)
+        return s;
+    switch (ti->getType()) {
+    case TreeItem::MapCenter:
+        s = "mc:";
+        break;
+    case TreeItem::Branch:
+        s = "bo:";
+        break;
+    case TreeItem::Image:
+        s = "fi:";
+        break;
+    case TreeItem::Attribute:
+        s = "ai:";
+        break;
+    case TreeItem::XLink:
+        s = "xl:";
+        break;
+    default:
+        s = "unknown type in VymModel::getSelectString()";
+        break;
+    }
+    s = s + QString("%1").arg(ti->num());
+    if (ti->depth() > 0)
+        // call myself recursively
+        s = getSelectString(ti->parent()) + "," + s;
+    return s;
+}
+
+QString VymModel::getSelectString(BranchItem *bi)
+{
+    return getSelectString((TreeItem *)bi);
+}
+
+QString VymModel::getSelectString(const uint &i)
+{
+    return getSelectString(findID(i));
+}
+
+void VymModel::setLatestAddedItem(TreeItem *ti) { latestAddedItem = ti; }
+
+TreeItem *VymModel::getLatestAddedItem() { return latestAddedItem; }
+
+SlideModel *VymModel::getSlideModel() { return slideModel; }
+
+int VymModel::slideCount() { return slideModel->count(); }
+
+SlideItem *VymModel::addSlide()
+{
+    SlideItem *si = slideModel->getSelectedItem();
+    if (si)
+        si = slideModel->addSlide(NULL, si->childNumber() + 1);
+    else
+        si = slideModel->addSlide();
+
+    TreeItem *seli = getSelectedItem();
+
+    if (si && seli) {
+        QString inScript;
+        if (!loadStringFromDisk(vymBaseDir.path() +
+                                    "/macros/slideeditor-snapshot.vys",
+                                inScript)) {
+            qWarning() << "VymModel::addSlide couldn't load template for "
+                          "taking snapshot";
+            return NULL;
+        }
+
+        inScript.replace(
+            "CURRENT_ZOOM",
+            QString().setNum(getMapEditor()->getZoomFactorTarget()));
+        inScript.replace("CURRENT_ANGLE",
+                         QString().setNum(getMapEditor()->getAngleTarget()));
+        inScript.replace("CURRENT_ID",
+                         "\"" + seli->getUuid().toString() + "\"");
+
+        si->setInScript(inScript);
+        slideModel->setData(slideModel->index(si), seli->getHeadingPlain());
+    }
+    QString s = "<vymmap>" + si->saveToDir() + "</vymmap>";
+    int pos = si->childNumber();
+    saveState(PartOfMap, getSelectString(),
+              QString("removeSlide (%1)").arg(pos), getSelectString(),
+              QString("addMapInsert (\"PATH\",%1)").arg(pos), "Add slide", NULL,
+              s);
+    return si;
+}
+
+void VymModel::deleteSlide(SlideItem *si)
+{
+    if (si) {
+        QString s = "<vymmap>" + si->saveToDir() + "</vymmap>";
+        int pos = si->childNumber();
+        saveState(PartOfMap, getSelectString(),
+                  QString("addMapInsert (\"PATH\",%1)").arg(pos),
+                  getSelectString(), QString("removeSlide (%1)").arg(pos),
+                  "Remove slide", NULL, s);
+        slideModel->deleteSlide(si);
+    }
+}
+
+void VymModel::deleteSlide(int n) { deleteSlide(slideModel->getSlide(n)); }
+
+void VymModel::relinkSlide(SlideItem *si, int pos)
+{
+    if (si && pos >= 0)
+        slideModel->relinkSlide(si, si->parent(), pos);
+}
+
+bool VymModel::moveSlideDown(int n)
+{
+    SlideItem *si = NULL;
+    if (n < 0) // default if called without parameters
+    {
+        si = slideModel->getSelectedItem();
+        if (si)
+            n = si->childNumber();
+        else
+            return false;
+    }
+    else
+        si = slideModel->getSlide(n);
+    if (si && n >= 0 && n < slideModel->count() - 1) {
+        blockSlideSelection = true;
+        slideModel->relinkSlide(si, si->parent(), n + 1);
+        blockSlideSelection = false;
+        saveState(getSelectString(), QString("moveSlideUp (%1)").arg(n + 1),
+                  getSelectString(), QString("moveSlideDown (%1)").arg(n),
+                  QString("Move slide %1 down").arg(n));
+        return true;
+    }
+    else
+        return false;
+}
+
+bool VymModel::moveSlideUp(int n)
+{
+    SlideItem *si = NULL;
+    if (n < 0) // default if called without parameters
+    {
+        si = slideModel->getSelectedItem();
+        if (si)
+            n = si->childNumber();
+        else
+            return false;
+    }
+    else
+        si = slideModel->getSlide(n);
+    if (si && n > 0 && n < slideModel->count()) {
+        blockSlideSelection = true;
+        slideModel->relinkSlide(si, si->parent(), n - 1);
+        blockSlideSelection = false;
+        saveState(getSelectString(), QString("moveSlideDown (%1)").arg(n - 1),
+                  getSelectString(), QString("moveSlideUp (%1)").arg(n),
+                  QString("Move slide %1 up").arg(n));
+        return true;
+    }
+    else
+        return false;
+}
+
+void VymModel::updateSlideSelection(QItemSelection newsel, QItemSelection)
+{
+    if (blockSlideSelection)
+        return;
+    QModelIndex ix;
+    foreach (ix, newsel.indexes()) {
+        SlideItem *si = static_cast<SlideItem *>(ix.internalPointer());
+        QString inScript = si->getInScript();
+
+        // show inScript in ScriptEditor
+        scriptEditor->setSlideScript(modelID, si->getID(), inScript);
+
+        // Execute inScript
+        execute(inScript);
+    }
+}